diff --git a/src/neo-vm/EvaluationStack.cs b/src/neo-vm/EvaluationStack.cs new file mode 100644 index 00000000..94f14767 --- /dev/null +++ b/src/neo-vm/EvaluationStack.cs @@ -0,0 +1,97 @@ +using Neo.VM.Types; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public sealed class EvaluationStack : IReadOnlyCollection + { + private readonly RandomAccessStack innerStack = new RandomAccessStack(); + private readonly ReferenceCounter referenceCounter; + + internal EvaluationStack(ReferenceCounter referenceCounter) + { + this.referenceCounter = referenceCounter; + } + + public int Count => innerStack.Count; + + internal void Clear() + { + foreach (StackItem item in innerStack) + referenceCounter.RemoveStackReference(item); + innerStack.Clear(); + } + + internal void CopyTo(EvaluationStack stack, int count = -1) + { + innerStack.CopyTo(stack.innerStack, count); + } + + public IEnumerator GetEnumerator() + { + return innerStack.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return innerStack.GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Insert(int index, StackItem item) + { + innerStack.Insert(index, item); + referenceCounter.AddStackReference(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Peek(int index = 0) + { + return innerStack.Peek(index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Pop() + { + if (!TryPop(out StackItem item)) + throw new InvalidOperationException(); + return item; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(StackItem item) + { + innerStack.Push(item); + referenceCounter.AddStackReference(item); + } + + internal void Set(int index, StackItem item) + { + StackItem old_item = innerStack.Peek(index); + referenceCounter.RemoveStackReference(old_item); + innerStack.Set(index, item); + referenceCounter.AddStackReference(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T item) where T : StackItem + { + return TryRemove(0, out item); + } + + internal bool TryRemove(int index, out T item) where T : StackItem + { + if (!innerStack.TryRemove(index, out StackItem stackItem)) + { + item = null; + return false; + } + referenceCounter.RemoveStackReference(stackItem); + item = stackItem as T; + return item != null; + } + } +} diff --git a/src/neo-vm/ExecutionContext.cs b/src/neo-vm/ExecutionContext.cs index b592ddfa..d49b2ab9 100644 --- a/src/neo-vm/ExecutionContext.cs +++ b/src/neo-vm/ExecutionContext.cs @@ -1,4 +1,3 @@ -using Neo.VM.Types; using System; using System.Collections.Generic; using System.Diagnostics; @@ -24,12 +23,12 @@ public sealed class ExecutionContext /// /// Evaluation stack /// - public RandomAccessStack EvaluationStack { get; } + public EvaluationStack EvaluationStack { get; } /// /// Alternative stack /// - public RandomAccessStack AltStack { get; } + public EvaluationStack AltStack { get; } /// /// Instruction pointer @@ -65,12 +64,12 @@ public Instruction NextInstruction /// Script /// The calling script /// Number of items to be returned - internal ExecutionContext(Script script, Script callingScript, int rvcount) - : this(script, callingScript, rvcount, new RandomAccessStack(), new RandomAccessStack()) + internal ExecutionContext(Script script, Script callingScript, int rvcount, ReferenceCounter referenceCounter) + : this(script, callingScript, rvcount, new EvaluationStack(referenceCounter), new EvaluationStack(referenceCounter)) { } - private ExecutionContext(Script script, Script callingScript, int rvcount, RandomAccessStack stack, RandomAccessStack alt) + private ExecutionContext(Script script, Script callingScript, int rvcount, EvaluationStack stack, EvaluationStack alt) { this.RVCount = rvcount; this.Script = script; diff --git a/src/neo-vm/ExecutionEngine.cs b/src/neo-vm/ExecutionEngine.cs index 310c92ef..fc55d0ea 100644 --- a/src/neo-vm/ExecutionEngine.cs +++ b/src/neo-vm/ExecutionEngine.cs @@ -1,7 +1,6 @@ using Neo.VM.Types; using System; using System.Collections.Generic; -using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; using VMArray = Neo.VM.Types.Array; @@ -42,32 +41,21 @@ public class ExecutionEngine : IDisposable /// public virtual uint MaxInvocationStackSize => 1024; - /// - /// Set Max Array Size - /// - public virtual uint MaxArraySize => 1024; - #endregion - private int stackitem_count = 0; - private bool is_stackitem_count_strict = true; - + public ReferenceCounter ReferenceCounter { get; } = new ReferenceCounter(); public RandomAccessStack InvocationStack { get; } = new RandomAccessStack(); - public RandomAccessStack ResultStack { get; } = new RandomAccessStack(); - - public ExecutionContext CurrentContext => InvocationStack.Count > 0 ? InvocationStack.Peek() : null; - public ExecutionContext EntryContext => InvocationStack.Count > 0 ? InvocationStack.Peek(InvocationStack.Count - 1) : null; + public ExecutionContext CurrentContext { get; private set; } + public ExecutionContext EntryContext { get; private set; } + public EvaluationStack ResultStack { get; } public VMState State { get; internal protected set; } = VMState.BREAK; - #region Limits + public ExecutionEngine() + { + ResultStack = new EvaluationStack(ReferenceCounter); + } - /// - /// Check if it is possible to overflow the MaxArraySize - /// - /// Length - /// Return True if are allowed, otherwise False - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool CheckArraySize(int length) => length <= MaxArraySize; + #region Limits /// /// Check if the is possible to overflow the MaxItemSize @@ -93,71 +81,6 @@ public class ExecutionEngine : IDisposable [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool CheckShift(int shift) => shift <= Max_SHL_SHR && shift >= Min_SHL_SHR; - /// - /// Check if the is possible to overflow the MaxStackSize - /// - /// Is stack count strict? - /// Stack item count - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool CheckStackSize(bool strict, int count = 1) - { - is_stackitem_count_strict &= strict; - stackitem_count += count; - - if (stackitem_count < 0) stackitem_count = int.MaxValue; - if (stackitem_count <= MaxStackSize) return true; - if (is_stackitem_count_strict) return false; - - // Deep inspect - - stackitem_count = GetItemCount(InvocationStack.Select(p => p.EvaluationStack).Distinct().Concat(InvocationStack.Select(p => p.AltStack).Distinct()).SelectMany(p => p)); - if (stackitem_count > MaxStackSize) return false; - is_stackitem_count_strict = true; - - return true; - } - - /// - /// Get item count - /// - /// Items - /// Return the number of items - private static int GetItemCount(IEnumerable items) - { - Queue queue = new Queue(items); - List counted = new List(); - int count = 0; - while (queue.Count > 0) - { - StackItem item = queue.Dequeue(); - count++; - switch (item) - { - case Types.Array array: - { - if (counted.Any(p => ReferenceEquals(p, array))) - continue; - counted.Add(array); - foreach (StackItem subitem in array) - queue.Enqueue(subitem); - break; - } - case Map map: - { - if (counted.Any(p => ReferenceEquals(p, map))) - continue; - counted.Add(map); - count += map.Count; // Count key items - foreach (StackItem subitem in map.Values) - queue.Enqueue(subitem); - break; - } - } - } - return count; - } - #endregion protected virtual void ContextUnloaded(ExecutionContext context) @@ -206,16 +129,14 @@ private bool ExecuteInstruction() if (instruction.OpCode >= OpCode.PUSHBYTES1 && instruction.OpCode <= OpCode.PUSHDATA4) { if (!CheckMaxItemSize(instruction.Operand.Length)) return false; - context.EvaluationStack.Push(instruction.Operand); - if (!CheckStackSize(true)) return false; + Push(instruction.Operand); } else switch (instruction.OpCode) { // Push value case OpCode.PUSH0: { - context.EvaluationStack.Push(ReadOnlyMemory.Empty); - if (!CheckStackSize(true)) return false; + Push(ReadOnlyMemory.Empty); break; } case OpCode.PUSHM1: @@ -236,14 +157,12 @@ private bool ExecuteInstruction() case OpCode.PUSH15: case OpCode.PUSH16: { - context.EvaluationStack.Push((int)instruction.OpCode - (int)OpCode.PUSH1 + 1); - if (!CheckStackSize(true)) return false; + Push((int)instruction.OpCode - (int)OpCode.PUSH1 + 1); break; } case OpCode.PUSHNULL: { - context.EvaluationStack.Push(StackItem.Null); - if (!CheckStackSize(true)) return false; + Push(StackItem.Null); break; } @@ -258,9 +177,8 @@ private bool ExecuteInstruction() bool fValue = true; if (instruction.OpCode > OpCode.JMP) { - CheckStackSize(false, -1); - fValue = context.EvaluationStack.Pop().ToBoolean(); - + if (!TryPop(out StackItem x)) return false; + fValue = x.ToBoolean(); if (instruction.OpCode == OpCode.JMPIFNOT) fValue = !fValue; } @@ -283,21 +201,32 @@ private bool ExecuteInstruction() ExecutionContext context_pop = InvocationStack.Pop(); int rvcount = context_pop.RVCount; if (rvcount == -1) rvcount = context_pop.EvaluationStack.Count; - if (rvcount > 0) + EvaluationStack stack_eval; + if (InvocationStack.Count == 0) { - if (context_pop.EvaluationStack.Count < rvcount) return false; - RandomAccessStack stack_eval; - if (InvocationStack.Count == 0) - stack_eval = ResultStack; - else - stack_eval = CurrentContext.EvaluationStack; - context_pop.EvaluationStack.CopyTo(stack_eval, rvcount); + EntryContext = null; + CurrentContext = null; + stack_eval = ResultStack; } - if (context_pop.RVCount == -1 && InvocationStack.Count > 0) + else { - context_pop.AltStack.CopyTo(CurrentContext.AltStack); + CurrentContext = InvocationStack.Peek(); + stack_eval = CurrentContext.EvaluationStack; + } + if (context_pop.EvaluationStack == stack_eval) + { + if (context_pop.RVCount != 0) return false; + } + else + { + if (context_pop.EvaluationStack.Count != rvcount) return false; + if (rvcount > 0) + context_pop.EvaluationStack.CopyTo(stack_eval); + } + if (InvocationStack.Count == 0 || context_pop.AltStack != CurrentContext.AltStack) + { + context_pop.AltStack.Clear(); } - CheckStackSize(false, 0); if (InvocationStack.Count == 0) { State = VMState.HALT; @@ -307,7 +236,7 @@ private bool ExecuteInstruction() } case OpCode.SYSCALL: { - if (!OnSysCall(instruction.TokenU32) || !CheckStackSize(false, int.MaxValue)) + if (!OnSysCall(instruction.TokenU32)) return false; break; } @@ -315,139 +244,124 @@ private bool ExecuteInstruction() // Stack ops case OpCode.DUPFROMALTSTACKBOTTOM: { - var item = context.AltStack.Peek(context.AltStack.Count - 1); - context.EvaluationStack.Push(item); - if (!CheckStackSize(true)) return false; + Push(context.AltStack.Peek(-1)); break; } case OpCode.DUPFROMALTSTACK: { - context.EvaluationStack.Push(context.AltStack.Peek()); - if (!CheckStackSize(true)) return false; + Push(context.AltStack.Peek()); break; } case OpCode.TOALTSTACK: { - context.AltStack.Push(context.EvaluationStack.Pop()); + if (!TryPop(out StackItem x)) return false; + context.AltStack.Push(x); break; } case OpCode.FROMALTSTACK: { - context.EvaluationStack.Push(context.AltStack.Pop()); + if (!context.AltStack.TryPop(out StackItem x)) return false; + Push(x); break; } case OpCode.ISNULL: { - bool b = context.EvaluationStack.Peek().IsNull; - context.EvaluationStack.Set(0, b); - CheckStackSize(false, 0); + if (!TryPop(out StackItem x)) return false; + Push(x.IsNull); break; } case OpCode.XDROP: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_n)) - return false; + if (!TryPop(out PrimitiveType item_n)) return false; int n = (int)item_n.ToBigInteger(); if (n < 0) return false; - context.EvaluationStack.Remove(n); - CheckStackSize(false, -2); + if (!context.EvaluationStack.TryRemove(n, out StackItem _)) return false; break; } case OpCode.XSWAP: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_n)) - return false; + if (!TryPop(out PrimitiveType item_n)) return false; int n = (int)item_n.ToBigInteger(); if (n < 0) return false; - CheckStackSize(true, -1); if (n == 0) break; - StackItem xn = context.EvaluationStack.Peek(n); - context.EvaluationStack.Set(n, context.EvaluationStack.Peek()); + StackItem xn = Peek(n); + context.EvaluationStack.Set(n, Peek()); context.EvaluationStack.Set(0, xn); break; } case OpCode.XTUCK: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_n)) - return false; + if (!TryPop(out PrimitiveType item_n)) return false; int n = (int)item_n.ToBigInteger(); if (n <= 0) return false; - context.EvaluationStack.Insert(n, context.EvaluationStack.Peek()); + context.EvaluationStack.Insert(n, Peek()); break; } case OpCode.DEPTH: { - context.EvaluationStack.Push(context.EvaluationStack.Count); - if (!CheckStackSize(true)) return false; + Push(context.EvaluationStack.Count); break; } case OpCode.DROP: { - context.EvaluationStack.Pop(); - CheckStackSize(false, -1); + if (!TryPop(out StackItem _)) return false; break; } case OpCode.DUP: { - context.EvaluationStack.Push(context.EvaluationStack.Peek()); - if (!CheckStackSize(true)) return false; + Push(Peek()); break; } case OpCode.NIP: { - context.EvaluationStack.Remove(1); - CheckStackSize(false, -1); + if (!context.EvaluationStack.TryRemove(1, out StackItem _)) return false; break; } case OpCode.OVER: { - context.EvaluationStack.Push(context.EvaluationStack.Peek(1)); - if (!CheckStackSize(true)) return false; + Push(Peek(1)); break; } case OpCode.PICK: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_n)) - return false; + if (!TryPop(out PrimitiveType item_n)) return false; int n = (int)item_n.ToBigInteger(); if (n < 0) return false; - context.EvaluationStack.Push(context.EvaluationStack.Peek(n)); + Push(Peek(n)); break; } case OpCode.ROLL: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_n)) - return false; + if (!TryPop(out PrimitiveType item_n)) return false; int n = (int)item_n.ToBigInteger(); if (n < 0) return false; - CheckStackSize(true, -1); if (n == 0) break; - context.EvaluationStack.Push(context.EvaluationStack.Remove(n)); + if (!context.EvaluationStack.TryRemove(n, out StackItem x)) return false; + Push(x); break; } case OpCode.ROT: { - context.EvaluationStack.Push(context.EvaluationStack.Remove(2)); + if (!context.EvaluationStack.TryRemove(2, out StackItem x)) return false; + Push(x); break; } case OpCode.SWAP: { - context.EvaluationStack.Push(context.EvaluationStack.Remove(1)); + if (!context.EvaluationStack.TryRemove(1, out StackItem x)) return false; + Push(x); break; } case OpCode.TUCK: { - context.EvaluationStack.Insert(2, context.EvaluationStack.Peek()); - if (!CheckStackSize(true)) return false; + context.EvaluationStack.Insert(2, Peek()); break; } case OpCode.CAT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; + if (!TryPop(out PrimitiveType item_x1)) return false; ReadOnlyMemory x2 = item_x2.ToMemory(); - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; ReadOnlyMemory x1 = item_x1.ToMemory(); StackItem result; if (x1.IsEmpty) @@ -467,465 +381,381 @@ private bool ExecuteInstruction() x2.CopyTo(dstBuffer.AsMemory(x1.Length)); result = dstBuffer; } - context.EvaluationStack.Push(result); - CheckStackSize(true, -1); + Push(result); break; } case OpCode.SUBSTR: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_count)) - return false; + if (!TryPop(out PrimitiveType item_count)) return false; int count = (int)item_count.ToBigInteger(); if (count < 0) return false; if (count > MaxItemSize) count = (int)MaxItemSize; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_index)) - return false; + if (!TryPop(out PrimitiveType item_index)) return false; int index = (int)item_index.ToBigInteger(); if (index < 0) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; ReadOnlyMemory x = item_x.ToMemory(); if (index > x.Length) return false; if (index + count > x.Length) count = x.Length - index; - context.EvaluationStack.Push(x.Slice(index, count)); - CheckStackSize(true, -2); + Push(x.Slice(index, count)); break; } case OpCode.LEFT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_count)) - return false; + if (!TryPop(out PrimitiveType item_count)) return false; int count = (int)item_count.ToBigInteger(); if (count < 0) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; ReadOnlyMemory x = item_x.ToMemory(); - if (count < x.Length) - x = x[0..count]; - context.EvaluationStack.Push(x); - CheckStackSize(true, -1); + if (count < x.Length) x = x[0..count]; + Push(x); break; } case OpCode.RIGHT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_count)) - return false; + if (!TryPop(out PrimitiveType item_count)) return false; int count = (int)item_count.ToBigInteger(); if (count < 0) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; ReadOnlyMemory x = item_x.ToMemory(); if (count > x.Length) return false; - if (count < x.Length) - x = x[^count..^0]; - context.EvaluationStack.Push(x); - CheckStackSize(true, -1); + if (count < x.Length) x = x[^count..^0]; + Push(x); break; } case OpCode.SIZE: { - if (!context.EvaluationStack.TryPop(out PrimitiveType x)) - return false; - context.EvaluationStack.Push(x.GetByteLength()); + if (!TryPop(out PrimitiveType x)) return false; + Push(x.GetByteLength()); break; } // Bitwise logic case OpCode.INVERT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(~x); + Push(~x); break; } case OpCode.AND: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 & x2); - CheckStackSize(true, -1); + Push(x1 & x2); break; } case OpCode.OR: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 | x2); - CheckStackSize(true, -1); + Push(x1 | x2); break; } case OpCode.XOR: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 ^ x2); - CheckStackSize(true, -1); + Push(x1 ^ x2); break; } case OpCode.EQUAL: { - StackItem x2 = context.EvaluationStack.Pop(); - StackItem x1 = context.EvaluationStack.Pop(); - context.EvaluationStack.Push(x1.Equals(x2)); - CheckStackSize(false, -1); + if (!TryPop(out StackItem x2)) return false; + if (!TryPop(out StackItem x1)) return false; + Push(x1.Equals(x2)); break; } // Numeric case OpCode.INC: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; x += 1; if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(x); + Push(x); break; } case OpCode.DEC: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; x -= 1; if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(x); + Push(x); break; } case OpCode.SIGN: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(x.Sign); + Push(x.Sign); break; } case OpCode.NEGATE: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(-x); + Push(-x); break; } case OpCode.ABS: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(BigInteger.Abs(x)); + Push(BigInteger.Abs(x)); break; } case OpCode.NOT: { - bool x = context.EvaluationStack.Pop().ToBoolean(); - context.EvaluationStack.Push(!x); - CheckStackSize(false, 0); + if (!TryPop(out StackItem x)) return false; + Push(!x.ToBoolean()); break; } case OpCode.NZ: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(!x.IsZero); + Push(!x.IsZero); break; } case OpCode.ADD: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; BigInteger result = x1 + x2; if (!CheckBigInteger(result)) return false; - context.EvaluationStack.Push(result); - CheckStackSize(true, -1); + Push(result); break; } case OpCode.SUB: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; BigInteger result = x1 - x2; if (!CheckBigInteger(result)) return false; - context.EvaluationStack.Push(result); - CheckStackSize(true, -1); + Push(result); break; } case OpCode.MUL: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; BigInteger result = x1 * x2; if (!CheckBigInteger(result)) return false; - context.EvaluationStack.Push(result); - CheckStackSize(true, -1); + Push(result); break; } case OpCode.DIV: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 / x2); - CheckStackSize(true, -1); + Push(x1 / x2); break; } case OpCode.MOD: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 % x2); - CheckStackSize(true, -1); + Push(x1 % x2); break; } case OpCode.SHL: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_shift)) - return false; + if (!TryPop(out PrimitiveType item_shift)) return false; int shift = (int)item_shift.ToBigInteger(); - CheckStackSize(true, -1); if (!CheckShift(shift)) return false; if (shift == 0) break; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; x <<= shift; if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(x); + Push(x); break; } case OpCode.SHR: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_shift)) - return false; + if (!TryPop(out PrimitiveType item_shift)) return false; int shift = (int)item_shift.ToBigInteger(); - CheckStackSize(true, -1); if (!CheckShift(shift)) return false; if (shift == 0) break; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; x >>= shift; if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(x); + Push(x); break; } case OpCode.BOOLAND: { - bool x2 = context.EvaluationStack.Pop().ToBoolean(); - bool x1 = context.EvaluationStack.Pop().ToBoolean(); - context.EvaluationStack.Push(x1 && x2); - CheckStackSize(false, -1); + if (!TryPop(out StackItem x2)) return false; + if (!TryPop(out StackItem x1)) return false; + Push(x1.ToBoolean() && x2.ToBoolean()); break; } case OpCode.BOOLOR: { - bool x2 = context.EvaluationStack.Pop().ToBoolean(); - bool x1 = context.EvaluationStack.Pop().ToBoolean(); - context.EvaluationStack.Push(x1 || x2); - CheckStackSize(false, -1); + if (!TryPop(out StackItem x2)) return false; + if (!TryPop(out StackItem x1)) return false; + Push(x1.ToBoolean() || x2.ToBoolean()); break; } case OpCode.NUMEQUAL: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 == x2); - CheckStackSize(true, -1); + Push(x1 == x2); break; } case OpCode.NUMNOTEQUAL: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 != x2); - CheckStackSize(true, -1); + Push(x1 != x2); break; } case OpCode.LT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 < x2); - CheckStackSize(true, -1); + Push(x1 < x2); break; } case OpCode.GT: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 > x2); - CheckStackSize(true, -1); + Push(x1 > x2); break; } case OpCode.LTE: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 <= x2); - CheckStackSize(true, -1); + Push(x1 <= x2); break; } case OpCode.GTE: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(x1 >= x2); - CheckStackSize(true, -1); + Push(x1 >= x2); break; } case OpCode.MIN: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(BigInteger.Min(x1, x2)); - CheckStackSize(true, -1); + Push(BigInteger.Min(x1, x2)); break; } case OpCode.MAX: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x2)) - return false; + if (!TryPop(out PrimitiveType item_x2)) return false; BigInteger x2 = item_x2.ToBigInteger(); if (!CheckBigInteger(x2)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x1)) - return false; + if (!TryPop(out PrimitiveType item_x1)) return false; BigInteger x1 = item_x1.ToBigInteger(); if (!CheckBigInteger(x1)) return false; - context.EvaluationStack.Push(BigInteger.Max(x1, x2)); - CheckStackSize(true, -1); + Push(BigInteger.Max(x1, x2)); break; } case OpCode.WITHIN: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_b)) - return false; + if (!TryPop(out PrimitiveType item_b)) return false; BigInteger b = item_b.ToBigInteger(); if (!CheckBigInteger(b)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_a)) - return false; + if (!TryPop(out PrimitiveType item_a)) return false; BigInteger a = item_a.ToBigInteger(); if (!CheckBigInteger(a)) return false; - if (!context.EvaluationStack.TryPop(out PrimitiveType item_x)) - return false; + if (!TryPop(out PrimitiveType item_x)) return false; BigInteger x = item_x.ToBigInteger(); if (!CheckBigInteger(x)) return false; - context.EvaluationStack.Push(a <= x && x < b); - CheckStackSize(true, -2); + Push(a <= x && x < b); break; } // Array case OpCode.ARRAYSIZE: { - switch (context.EvaluationStack.Pop()) + if (!TryPop(out StackItem x)) return false; + switch (x) { case CompoundType compound: - context.EvaluationStack.Push(compound.Count); - CheckStackSize(false, 0); + Push(compound.Count); break; case PrimitiveType primitive: - context.EvaluationStack.Push(primitive.GetByteLength()); - CheckStackSize(true, 0); + Push(primitive.GetByteLength()); break; default: return false; @@ -934,46 +764,44 @@ private bool ExecuteInstruction() } case OpCode.PACK: { - if (!context.EvaluationStack.TryPop(out PrimitiveType item_size)) - return false; + if (!TryPop(out PrimitiveType item_size)) return false; int size = (int)item_size.ToBigInteger(); - if (size < 0 || size > context.EvaluationStack.Count || !CheckArraySize(size)) + if (size < 0 || size > context.EvaluationStack.Count) return false; - List items = new List(size); + VMArray array = new VMArray(ReferenceCounter); for (int i = 0; i < size; i++) - items.Add(context.EvaluationStack.Pop()); - context.EvaluationStack.Push(items); + { + if (!TryPop(out StackItem item)) return false; + array.Add(item); + } + Push(array); break; } case OpCode.UNPACK: { - if (!context.EvaluationStack.TryPop(out VMArray array)) - return false; + if (!TryPop(out VMArray array)) return false; for (int i = array.Count - 1; i >= 0; i--) - context.EvaluationStack.Push(array[i]); - context.EvaluationStack.Push(array.Count); - if (!CheckStackSize(false, array.Count)) return false; + Push(array[i]); + Push(array.Count); break; } case OpCode.PICKITEM: { - if (!context.EvaluationStack.TryPop(out PrimitiveType key)) - return false; - switch (context.EvaluationStack.Pop()) + if (!TryPop(out PrimitiveType key)) return false; + if (!TryPop(out StackItem x)) return false; + switch (x) { case VMArray array: { int index = (int)key.ToBigInteger(); if (index < 0 || index >= array.Count) return false; - context.EvaluationStack.Push(array[index]); - CheckStackSize(false, -1); + Push(array[index]); break; } case Map map: { if (!map.TryGetValue(key, out StackItem value)) return false; - context.EvaluationStack.Push(value); - CheckStackSize(false, -1); + Push(value); break; } case PrimitiveType primitive: @@ -981,8 +809,7 @@ private bool ExecuteInstruction() ReadOnlySpan byteArray = primitive.ToByteArray(); int index = (int)key.ToBigInteger(); if (index < 0 || index >= byteArray.Length) return false; - context.EvaluationStack.Push((int)byteArray[index]); - CheckStackSize(true, -1); + Push((BigInteger)byteArray[index]); break; } default: @@ -992,72 +819,65 @@ private bool ExecuteInstruction() } case OpCode.SETITEM: { - StackItem value = context.EvaluationStack.Pop(); + if (!TryPop(out StackItem value)) return false; if (value is Struct s) value = s.Clone(); - if (!context.EvaluationStack.TryPop(out PrimitiveType key)) - return false; - switch (context.EvaluationStack.Pop()) + if (!TryPop(out PrimitiveType key)) return false; + if (!TryPop(out StackItem x)) return false; + switch (x) { case VMArray array: { int index = (int)key.ToBigInteger(); - if (index < 0 || index >= array.Count) return false; array[index] = value; break; } case Map map: { - if (!map.ContainsKey(key) && !CheckArraySize(map.Count + 1)) - return false; map[key] = value; break; } default: return false; } - - if (!CheckStackSize(false, int.MaxValue)) - return false; - break; } case OpCode.NEWARRAY: case OpCode.NEWSTRUCT: { - switch (context.EvaluationStack.Peek()) + if (!TryPop(out StackItem x)) return false; + switch (x) { + // Allow to convert between array and struct case VMArray array: { - // Allow to convert between array and struct + VMArray result; if (array is Struct) { if (instruction.OpCode == OpCode.NEWSTRUCT) - break; + result = array; + else + result = new VMArray(ReferenceCounter, array); } else { if (instruction.OpCode == OpCode.NEWARRAY) - break; + result = array; + else + result = new Struct(ReferenceCounter, array); } - VMArray result = instruction.OpCode == OpCode.NEWARRAY - ? new VMArray(array) - : new Struct(array); - context.EvaluationStack.Set(0, result); - if (!CheckStackSize(false, int.MaxValue)) return false; + Push(result); } break; case PrimitiveType primitive: { int count = (int)primitive.ToBigInteger(); - if (count < 0 || !CheckArraySize(count)) return false; - List items = new List(count); - for (var i = 0; i < count; i++) - items.Add(StackItem.Null); + if (count < 0 || count > MaxStackSize) return false; VMArray result = instruction.OpCode == OpCode.NEWARRAY - ? new VMArray(items) - : new Struct(items); - context.EvaluationStack.Set(0, result); - if (!CheckStackSize(true, count)) return false; + ? new VMArray(ReferenceCounter) + : new Struct(ReferenceCounter); + for (var i = 0; i < count; i++) + result.Add(StackItem.Null); + Push(result); } break; default: @@ -1067,46 +887,35 @@ private bool ExecuteInstruction() } case OpCode.NEWMAP: { - context.EvaluationStack.Push(new Map()); - if (!CheckStackSize(true)) return false; + Push(new Map(ReferenceCounter)); break; } case OpCode.APPEND: { - StackItem newItem = context.EvaluationStack.Pop(); + if (!TryPop(out StackItem newItem)) return false; + if (!TryPop(out VMArray array)) return false; if (newItem is Struct s) newItem = s.Clone(); - if (!context.EvaluationStack.TryPop(out VMArray array)) - return false; - if (!CheckArraySize(array.Count + 1)) return false; array.Add(newItem); - if (!CheckStackSize(false, int.MaxValue)) return false; break; } case OpCode.REVERSE: { - if (!context.EvaluationStack.TryPop(out VMArray array)) - return false; - CheckStackSize(false, -1); + if (!TryPop(out VMArray array)) return false; array.Reverse(); break; } case OpCode.REMOVE: { - if (!context.EvaluationStack.TryPop(out PrimitiveType key)) - return false; - StackItem value = context.EvaluationStack.Pop(); - CheckStackSize(false, -2); - switch (value) + if (!TryPop(out PrimitiveType key)) return false; + if (!TryPop(out StackItem x)) return false; + switch (x) { case VMArray array: int index = (int)key.ToBigInteger(); - if (index < 0 || index >= array.Count) return false; array.RemoveAt(index); - CheckStackSize(false, -1); break; case Map map: - if (map.Remove(key)) - CheckStackSize(false, -2); + map.Remove(key); break; default: return false; @@ -1115,35 +924,34 @@ private bool ExecuteInstruction() } case OpCode.HASKEY: { - if (!context.EvaluationStack.TryPop(out PrimitiveType key)) - return false; - switch (context.EvaluationStack.Pop()) + if (!TryPop(out PrimitiveType key)) return false; + if (!TryPop(out StackItem x)) return false; + switch (x) { case VMArray array: int index = (int)key.ToBigInteger(); if (index < 0) return false; - context.EvaluationStack.Push(index < array.Count); + Push(index < array.Count); break; case Map map: - context.EvaluationStack.Push(map.ContainsKey(key)); + Push(map.ContainsKey(key)); break; default: return false; } - CheckStackSize(false, -1); break; } case OpCode.KEYS: { - if (!context.EvaluationStack.TryPop(out Map map)) return false; - context.EvaluationStack.Push(new VMArray(map.Keys)); - if (!CheckStackSize(false, map.Count)) return false; + if (!TryPop(out Map map)) return false; + Push(new VMArray(ReferenceCounter, map.Keys)); break; } case OpCode.VALUES: { - ICollection values; - switch (context.EvaluationStack.Pop()) + IEnumerable values; + if (!TryPop(out StackItem x)) return false; + switch (x) { case VMArray array: values = array; @@ -1154,14 +962,13 @@ private bool ExecuteInstruction() default: return false; } - List newArray = new List(values.Count); + VMArray newArray = new VMArray(ReferenceCounter); foreach (StackItem item in values) if (item is Struct s) newArray.Add(s.Clone()); else newArray.Add(item); - context.EvaluationStack.Push(new VMArray(newArray)); - if (!CheckStackSize(false, int.MaxValue)) return false; + Push(newArray); break; } @@ -1172,9 +979,8 @@ private bool ExecuteInstruction() } case OpCode.THROWIFNOT: { - if (!context.EvaluationStack.Pop().ToBoolean()) - return false; - CheckStackSize(false, -1); + if (!TryPop(out StackItem x)) return false; + if (!x.ToBoolean()) return false; break; } default: @@ -1189,19 +995,48 @@ protected virtual void LoadContext(ExecutionContext context) if (InvocationStack.Count >= MaxInvocationStackSize) throw new InvalidOperationException(); InvocationStack.Push(context); + if (EntryContext is null) EntryContext = context; + CurrentContext = context; } public ExecutionContext LoadScript(Script script, int rvcount = -1) { - ExecutionContext context = new ExecutionContext(script, CurrentContext?.Script, rvcount); + ExecutionContext context = new ExecutionContext(script, CurrentContext?.Script, rvcount, ReferenceCounter); LoadContext(context); return context; } protected virtual bool OnSysCall(uint method) => false; - protected virtual bool PostExecuteInstruction(Instruction instruction) => true; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Peek(int index = 0) + { + return CurrentContext.EvaluationStack.Peek(index); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Pop() + { + return CurrentContext.EvaluationStack.Pop(); + } + + protected virtual bool PostExecuteInstruction(Instruction instruction) + { + return ReferenceCounter.CheckZeroReferred() <= MaxStackSize; + } protected virtual bool PreExecuteInstruction() => true; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(StackItem item) + { + CurrentContext.EvaluationStack.Push(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T item) where T : StackItem + { + return CurrentContext.EvaluationStack.TryPop(out item); + } } } diff --git a/src/neo-vm/RandomAccessStack.cs b/src/neo-vm/RandomAccessStack.cs index 947806f8..74c98272 100644 --- a/src/neo-vm/RandomAccessStack.cs +++ b/src/neo-vm/RandomAccessStack.cs @@ -65,44 +65,20 @@ public T Pop() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPop(out T item) - { - var index = list.Count - 1; - - if (index >= 0) - { - item = list[index]; - list.RemoveAt(index); - return true; - } - - item = default; - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryPop(out TItem item) where TItem : T + public void Push(T item) { - var index = list.Count - 1; - - if (index >= 0 && list[index] is TItem i) - { - item = i; - list.RemoveAt(index); - return true; - } - - item = default; - return false; + list.Add(item); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Push(T item) + public T Remove(int index) { - list.Add(item); + if (!TryRemove(index, out T item)) + throw new InvalidOperationException(); + return item; } - public T Remove(int index) + public void Set(int index, T item) { if (index >= list.Count) throw new InvalidOperationException(); if (index < 0) @@ -110,21 +86,35 @@ public T Remove(int index) index += list.Count; if (index < 0) throw new InvalidOperationException(); } - index = list.Count - index - 1; - T item = list[index]; - list.RemoveAt(index); - return item; + list[(list.Count - index - 1)] = item; } - public void Set(int index, T item) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T item) { - if (index >= list.Count) throw new InvalidOperationException(); + return TryRemove(0, out item); + } + + public bool TryRemove(int index, out T item) + { + if (index >= list.Count) + { + item = default; + return false; + } if (index < 0) { index += list.Count; - if (index < 0) throw new InvalidOperationException(); + if (index < 0) + { + item = default; + return false; + } } - list[(list.Count - index - 1)] = item; + index = list.Count - index - 1; + item = list[index]; + list.RemoveAt(index); + return true; } } } diff --git a/src/neo-vm/ReferenceCounter.cs b/src/neo-vm/ReferenceCounter.cs new file mode 100644 index 00000000..d0549cb4 --- /dev/null +++ b/src/neo-vm/ReferenceCounter.cs @@ -0,0 +1,111 @@ +using Neo.VM.Types; +using System.Collections.Generic; + +namespace Neo.VM +{ + public sealed class ReferenceCounter + { + private class Entry + { + public int StackReferences; + public Dictionary ObjectReferences; + } + + private readonly Dictionary counter = new Dictionary(ReferenceEqualityComparer.Default); + private readonly List zero_referred = new List(); + private int references_count = 0; + + public int Count => references_count; + + internal void AddReference(StackItem referred, CompoundType parent) + { + references_count++; + if (!(referred is CompoundType compound)) return; + if (!counter.TryGetValue(compound, out Entry tracing)) + { + tracing = new Entry(); + counter.Add(compound, tracing); + } + int count; + if (tracing.ObjectReferences is null) + { + tracing.ObjectReferences = new Dictionary(ReferenceEqualityComparer.Default); + count = 1; + } + else + { + if (tracing.ObjectReferences.TryGetValue(parent, out count)) + count++; + else + count = 1; + } + tracing.ObjectReferences[parent] = count; + } + + internal void AddStackReference(StackItem referred) + { + references_count++; + if (!(referred is CompoundType compound)) return; + if (counter.TryGetValue(compound, out Entry entry)) + entry.StackReferences++; + else + counter.Add(compound, new Entry { StackReferences = 1 }); + } + + internal int CheckZeroReferred() + { + if (zero_referred.Count > 0) + { + HashSet toBeDestroyed = new HashSet(ReferenceEqualityComparer.Default); + foreach (CompoundType compound in zero_referred) + { + HashSet toBeDestroyedInLoop = new HashSet(ReferenceEqualityComparer.Default); + Queue toBeChecked = new Queue(); + toBeChecked.Enqueue(compound); + while (toBeChecked.Count > 0) + { + CompoundType c = toBeChecked.Dequeue(); + Entry entry = counter[c]; + if (entry.StackReferences > 0) + { + toBeDestroyedInLoop.Clear(); + break; + } + toBeDestroyedInLoop.Add(c); + if (entry.ObjectReferences is null) continue; + foreach (var pair in entry.ObjectReferences) + if (pair.Value > 0 && !toBeDestroyed.Contains(pair.Key) && !toBeDestroyedInLoop.Contains(pair.Key)) + toBeChecked.Enqueue(pair.Key); + } + if (toBeDestroyedInLoop.Count > 0) + toBeDestroyed.UnionWith(toBeDestroyedInLoop); + } + foreach (CompoundType compound in toBeDestroyed) + { + counter.Remove(compound); + references_count -= compound.ItemsCount; + } + zero_referred.Clear(); + } + return references_count; + } + + internal void RemoveReference(StackItem referred, CompoundType parent) + { + references_count--; + if (!(referred is CompoundType compound)) return; + Entry entry = counter[compound]; + entry.ObjectReferences[parent] -= 1; + if (entry.StackReferences == 0) + zero_referred.Add(compound); + } + + internal void RemoveStackReference(StackItem referred) + { + references_count--; + if (!(referred is CompoundType item_compound)) return; + if (--counter[item_compound].StackReferences == 0) + zero_referred.Add(item_compound); + } + } +} diff --git a/src/neo-vm/ReferenceEqualityComparer.cs b/src/neo-vm/ReferenceEqualityComparer.cs new file mode 100644 index 00000000..bd203d6b --- /dev/null +++ b/src/neo-vm/ReferenceEqualityComparer.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer + { + public static readonly ReferenceEqualityComparer Default = new ReferenceEqualityComparer(); + + private ReferenceEqualityComparer() + { + } + + public new bool Equals(object x, object y) + { + return x == y; + } + + public int GetHashCode(object obj) + { + return RuntimeHelpers.GetHashCode(obj); + } + } +} diff --git a/src/neo-vm/Types/Array.cs b/src/neo-vm/Types/Array.cs index 72033a66..dc38be38 100644 --- a/src/neo-vm/Types/Array.cs +++ b/src/neo-vm/Types/Array.cs @@ -1,53 +1,54 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; namespace Neo.VM.Types { [DebuggerDisplay("Type={GetType().Name}, Count={Count}")] - public class Array : CompoundType, IList + public class Array : CompoundType, IReadOnlyList { protected readonly List _array; public StackItem this[int index] { - get => _array[index]; - set => _array[index] = value; + get + { + return _array[index]; + } + set + { + ReferenceCounter?.RemoveReference(_array[index], this); + _array[index] = value; + ReferenceCounter?.AddReference(value, this); + } } public override int Count => _array.Count; - public bool IsReadOnly => false; + public override int ItemsCount => _array.Count; - public Array() + public Array(IEnumerable value = null) + : this(null, value) { - _array = new List(); } - public Array(IEnumerable value) + public Array(ReferenceCounter referenceCounter, IEnumerable value = null) + : base(referenceCounter) { - _array = value as List ?? value.ToList(); + _array = value switch + { + null => new List(), + List list => list, + _ => new List(value) + }; + if (referenceCounter != null) + foreach (StackItem item in _array) + referenceCounter.AddReference(item, this); } public void Add(StackItem item) { _array.Add(item); - } - - public override void Clear() - { - _array.Clear(); - } - - public bool Contains(StackItem item) - { - return _array.Contains(item); - } - - void ICollection.CopyTo(StackItem[] array, int arrayIndex) - { - _array.CopyTo(array, arrayIndex); + ReferenceCounter?.AddReference(item, this); } IEnumerator IEnumerable.GetEnumerator() @@ -60,23 +61,9 @@ public IEnumerator GetEnumerator() return _array.GetEnumerator(); } - int IList.IndexOf(StackItem item) - { - return _array.IndexOf(item); - } - - public void Insert(int index, StackItem item) - { - _array.Insert(index, item); - } - - bool ICollection.Remove(StackItem item) - { - return _array.Remove(item); - } - public void RemoveAt(int index) { + ReferenceCounter?.RemoveReference(_array[index], this); _array.RemoveAt(index); } @@ -84,17 +71,5 @@ public void Reverse() { _array.Reverse(); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Array(StackItem[] value) - { - return new Array(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Array(List value) - { - return new Array(value); - } } } diff --git a/src/neo-vm/Types/CompoundType.cs b/src/neo-vm/Types/CompoundType.cs index 86832548..160f0769 100644 --- a/src/neo-vm/Types/CompoundType.cs +++ b/src/neo-vm/Types/CompoundType.cs @@ -1,14 +1,19 @@ using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; namespace Neo.VM.Types { public abstract class CompoundType : StackItem { + protected readonly ReferenceCounter ReferenceCounter; + + protected CompoundType(ReferenceCounter referenceCounter) + { + this.ReferenceCounter = referenceCounter; + } + public abstract int Count { get; } - public abstract void Clear(); + public abstract int ItemsCount { get; } public override bool Equals(StackItem other) { @@ -24,17 +29,5 @@ public override bool ToBoolean() { return true; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator CompoundType(StackItem[] value) - { - return (Array)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator CompoundType(List value) - { - return (Array)value; - } } } diff --git a/src/neo-vm/Types/Map.cs b/src/neo-vm/Types/Map.cs index 3bb61665..47393a2b 100644 --- a/src/neo-vm/Types/Map.cs +++ b/src/neo-vm/Types/Map.cs @@ -1,53 +1,54 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; namespace Neo.VM.Types { [DebuggerDisplay("Type={GetType().Name}, Count={Count}")] - public class Map : CompoundType, IDictionary + public class Map : CompoundType, IReadOnlyDictionary { private readonly Dictionary dictionary; public StackItem this[PrimitiveType key] { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => dictionary[key]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - set => dictionary[key] = value; + get + { + return dictionary[key]; + } + set + { + if (ReferenceCounter != null) + { + if (dictionary.TryGetValue(key, out StackItem old_value)) + ReferenceCounter.RemoveReference(old_value, this); + else + ReferenceCounter.AddReference(key, this); + ReferenceCounter.AddReference(value, this); + } + dictionary[key] = value; + } } - public ICollection Keys => dictionary.Keys; - public ICollection Values => dictionary.Values; public override int Count => dictionary.Count; - public bool IsReadOnly => false; + public override int ItemsCount => dictionary.Count * 2; + public IEnumerable Keys => dictionary.Keys; + public IEnumerable Values => dictionary.Values; - public Map() : this(new Dictionary()) { } - - public Map(Dictionary value) - { - dictionary = value; - } - - public void Add(PrimitiveType key, StackItem value) - { - dictionary.Add(key, value); - } - - void ICollection>.Add(KeyValuePair item) - { - dictionary.Add(item.Key, item.Value); - } - - public override void Clear() + public Map(Dictionary value = null) + : this(null, value) { - dictionary.Clear(); } - bool ICollection>.Contains(KeyValuePair item) + public Map(ReferenceCounter referenceCounter, Dictionary value = null) + : base(referenceCounter) { - return dictionary.ContainsKey(item.Key); + dictionary = value ?? new Dictionary(); + if (referenceCounter != null) + foreach (var pair in dictionary) + { + referenceCounter.AddReference(pair.Key, this); + referenceCounter.AddReference(pair.Value, this); + } } public bool ContainsKey(PrimitiveType key) @@ -55,12 +56,6 @@ public bool ContainsKey(PrimitiveType key) return dictionary.ContainsKey(key); } - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - foreach (KeyValuePair item in dictionary) - array[arrayIndex++] = item; - } - IEnumerator> IEnumerable>.GetEnumerator() { return dictionary.GetEnumerator(); @@ -73,12 +68,11 @@ IEnumerator IEnumerable.GetEnumerator() public bool Remove(PrimitiveType key) { - return dictionary.Remove(key); - } - - bool ICollection>.Remove(KeyValuePair item) - { - return dictionary.Remove(item.Key); + if (!dictionary.Remove(key, out StackItem old_value)) + return false; + ReferenceCounter?.RemoveReference(key, this); + ReferenceCounter?.RemoveReference(old_value, this); + return true; } public bool TryGetValue(PrimitiveType key, out StackItem value) diff --git a/src/neo-vm/Types/StackItem.cs b/src/neo-vm/Types/StackItem.cs index 99170eb4..dd1a9806 100644 --- a/src/neo-vm/Types/StackItem.cs +++ b/src/neo-vm/Types/StackItem.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; @@ -85,17 +84,5 @@ public static implicit operator StackItem(string value) { return (ByteArray)value; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator StackItem(StackItem[] value) - { - return (Array)value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator StackItem(List value) - { - return (Array)value; - } } } diff --git a/src/neo-vm/Types/Struct.cs b/src/neo-vm/Types/Struct.cs index 1c720b3a..7b6a7661 100644 --- a/src/neo-vm/Types/Struct.cs +++ b/src/neo-vm/Types/Struct.cs @@ -1,21 +1,26 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; namespace Neo.VM.Types { [DebuggerDisplay("Type={GetType().Name}, Count={Count}")] public class Struct : Array { - public Struct() : base() { } + public Struct(IEnumerable value = null) + : this(null, value) + { + } - public Struct(IEnumerable value) : base(value) { } + public Struct(ReferenceCounter referenceCounter, IEnumerable value = null) + : base(referenceCounter, value) + { + } public Struct Clone() { - Struct @struct = new Struct(); + Struct result = new Struct(ReferenceCounter); Queue queue = new Queue(); - queue.Enqueue(@struct); + queue.Enqueue(result); queue.Enqueue(this); while (queue.Count > 0) { @@ -25,7 +30,7 @@ public Struct Clone() { if (item is Struct sb) { - Struct sa = new Struct(); + Struct sa = new Struct(ReferenceCounter); a.Add(sa); queue.Enqueue(sa); queue.Enqueue(sb); @@ -36,7 +41,7 @@ public Struct Clone() } } } - return @struct; + return result; } public override bool Equals(StackItem other) @@ -67,17 +72,5 @@ public override bool Equals(StackItem other) } return true; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Struct(StackItem[] value) - { - return new Struct(value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator Struct(List value) - { - return new Struct(value); - } } } diff --git a/tests/neo-vm.Tests/UtExecutionContext.cs b/tests/neo-vm.Tests/UtExecutionContext.cs index 8985ea47..a301eae7 100644 --- a/tests/neo-vm.Tests/UtExecutionContext.cs +++ b/tests/neo-vm.Tests/UtExecutionContext.cs @@ -9,7 +9,7 @@ public class UtExecutionContext [TestMethod] public void StateTest() { - var context = new ExecutionContext(null, null, 0); + var context = new ExecutionContext(null, null, 0, new ReferenceCounter()); Assert.IsFalse(context.TryGetState(out var i)); context.SetState(5); diff --git a/tests/neo-vm.Tests/UtExecutionEngine.cs b/tests/neo-vm.Tests/UtExecutionEngine.cs new file mode 100644 index 00000000..8ab4afb9 --- /dev/null +++ b/tests/neo-vm.Tests/UtExecutionEngine.cs @@ -0,0 +1,105 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; + +namespace Neo.Test +{ + [TestClass] + public class UtExecutionEngine + { + [TestMethod] + public void TestReferenceTracing() + { + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitPush(0); //{0}{}:1 + sb.Emit(OpCode.NEWARRAY); //{A[]}|{}:1 + sb.Emit(OpCode.DUP); //{A[],A[]}|{}:2 + sb.Emit(OpCode.DUP); //{A[],A[],A[]}|{}:3 + sb.Emit(OpCode.APPEND); //{A[A]}|{}:2 + sb.Emit(OpCode.DUP); //{A[A],A[A]}|{}:3 + sb.EmitPush(0); //{A[A],A[A],0}|{}:4 + sb.Emit(OpCode.NEWARRAY); //{A[A],A[A],B[]}|{}:4 + sb.Emit(OpCode.TOALTSTACK); //{A[A],A[A]}|{B[]}:4 + sb.Emit(OpCode.DUPFROMALTSTACK); //{A[A],A[A],B[]}|{B[]}:5 + sb.Emit(OpCode.APPEND); //{A[A,B]}|{B[]}:4 + sb.Emit(OpCode.DUPFROMALTSTACK); //{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.DUPFROMALTSTACK); //{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.FROMALTSTACK); //{A[A,B],B[C[D[B]]]}|{}:7 + sb.Emit(OpCode.DROP); //{A[A,B[C[D[B]]]]}|{}:6 + sb.Emit(OpCode.DUP); //{A[A,B[C[D[B]]]],A[A,B]}|{}:7 + sb.EmitPush(1); //{A[A,B[C[D[B]]]],A[A,B],1}|{}:8 + sb.Emit(OpCode.REMOVE); //{A[A]}|{}:2 + sb.Emit(OpCode.TOALTSTACK); //{}|{A[A]}:2 + sb.Emit(OpCode.RET); //{}:0 + using ExecutionEngine engine = new ExecutionEngine(); + Debugger debugger = new Debugger(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(1, 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(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(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(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(8, 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.HALT, debugger.Execute()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + } + } +} diff --git a/tests/neo-vm.Tests/UtRandomAccessStack.cs b/tests/neo-vm.Tests/UtRandomAccessStack.cs index 50ebcf8f..3eb29c83 100644 --- a/tests/neo-vm.Tests/UtRandomAccessStack.cs +++ b/tests/neo-vm.Tests/UtRandomAccessStack.cs @@ -110,7 +110,7 @@ public void TestTryPopPush() { var stack = CreateOrderedStack(3); - Assert.IsTrue(stack.TryPop(out var item) && item == 3); + Assert.IsTrue(stack.TryPop(out var item) && item == 3); Assert.IsTrue(stack.TryPop(out item) && item == 2); Assert.IsTrue(stack.TryPop(out item) && item == 1); Assert.IsFalse(stack.TryPop(out item) && item == 0); diff --git a/tests/neo-vm.Tests/UtStackItem.cs b/tests/neo-vm.Tests/UtStackItem.cs index 9bb1e7a1..a370b218 100644 --- a/tests/neo-vm.Tests/UtStackItem.cs +++ b/tests/neo-vm.Tests/UtStackItem.cs @@ -2,7 +2,6 @@ using Neo.VM.Types; using System.Collections.Generic; using System.Numerics; -using System.Text; namespace Neo.Test { @@ -98,22 +97,6 @@ public void CastTest() Assert.IsInstanceOfType(item, typeof(ByteArray)); CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, ((ByteArray)item).ToByteArray().ToArray()); - // Array - - item = new StackItem[] { true, false }; - - Assert.IsInstanceOfType(item, typeof(Array)); - Assert.IsTrue(((Array)item)[0].ToBoolean()); - Assert.IsFalse(((Array)item)[1].ToBoolean()); - - // List - - item = new List(new StackItem[] { true, false }); - - Assert.IsInstanceOfType(item, typeof(Array)); - Assert.IsTrue(((Array)item)[0].ToBoolean()); - Assert.IsFalse(((Array)item)[1].ToBoolean()); - // Interop var interop = new InteropInterface>(new Dictionary() { { 1, 1 } }); diff --git a/tests/neo-vm.Tests/VMJsonTestBase.cs b/tests/neo-vm.Tests/VMJsonTestBase.cs index 3f29e0c4..835012f3 100644 --- a/tests/neo-vm.Tests/VMJsonTestBase.cs +++ b/tests/neo-vm.Tests/VMJsonTestBase.cs @@ -101,7 +101,7 @@ private void AssertResult(RandomAccessStack stack, VMUTExecuti /// Stack /// Result /// Message - private void AssertResult(RandomAccessStack stack, VMUTStackItem[] result, string message) + private void AssertResult(EvaluationStack stack, VMUTStackItem[] result, string message) { AssertAreEqual(stack.Count, result == null ? 0 : result.Length, message + "Stack is different");