Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Commit

Permalink
Ensure the behavior of Map is deterministic (#259)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikzhang authored Dec 11, 2019
1 parent 9a29bd4 commit be1903e
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 27 deletions.
112 changes: 112 additions & 0 deletions src/neo-vm/Collections/OrderedDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Neo.VM.Collections
{
internal class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
private class TItem
{
public TKey Key;
public TValue Value;
}

private class InternalCollection : KeyedCollection<TKey, TItem>
{
protected override TKey GetKeyForItem(TItem item)
{
return item.Key;
}
}

private readonly InternalCollection collection = new InternalCollection();

public int Count => collection.Count;
public bool IsReadOnly => false;
public ICollection<TKey> Keys => collection.Select(p => p.Key).ToArray();
public ICollection<TValue> Values => collection.Select(p => p.Value).ToArray();

public TValue this[TKey key]
{
get
{
return collection[key].Value;
}
set
{
if (collection.Contains(key))
collection[key].Value = value;
else
Add(key, value);
}
}

public void Add(TKey key, TValue value)
{
collection.Add(new TItem
{
Key = key,
Value = value
});
}

public bool ContainsKey(TKey key)
{
return collection.Contains(key);
}

public bool Remove(TKey key)
{
return collection.Remove(key);
}

public bool TryGetValue(TKey key, out TValue value)
{
if (collection.Contains(key))
{
value = collection[key].Value;
return true;
}
value = default;
return false;
}

void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}

public void Clear()
{
collection.Clear();
}

bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return collection.Contains(item.Key);
}

void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
for (int i = 0; i < collection.Count; i++)
array[i + arrayIndex] = new KeyValuePair<TKey, TValue>(collection[i].Key, collection[i].Value);
}

bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
return collection.Remove(item.Key);
}

IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return collection.Select(p => new KeyValuePair<TKey, TValue>(p.Key, p.Value)).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return collection.Select(p => new KeyValuePair<TKey, TValue>(p.Key, p.Value)).GetEnumerator();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using System.Linq;
using System.Runtime.CompilerServices;

namespace Neo.VM
namespace Neo.VM.Collections
{
[DebuggerDisplay("Count={Count}")]
public class RandomAccessStack<T> : IReadOnlyCollection<T>
internal class RandomAccessStack<T> : IReadOnlyCollection<T>
{
private readonly List<T> list = new List<T>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Neo.VM
namespace Neo.VM.Collections
{
internal sealed class ReferenceEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
Expand Down
1 change: 1 addition & 0 deletions src/neo-vm/EvaluationStack.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Neo.VM.Collections;
using Neo.VM.Types;
using System;
using System.Collections;
Expand Down
2 changes: 1 addition & 1 deletion src/neo-vm/ExecutionEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class ExecutionEngine : IDisposable
#endregion

public ReferenceCounter ReferenceCounter { get; } = new ReferenceCounter();
public RandomAccessStack<ExecutionContext> InvocationStack { get; } = new RandomAccessStack<ExecutionContext>();
public Stack<ExecutionContext> InvocationStack { get; } = new Stack<ExecutionContext>();
public ExecutionContext CurrentContext { get; private set; }
public ExecutionContext EntryContext { get; private set; }
public EvaluationStack ResultStack { get; }
Expand Down
1 change: 1 addition & 0 deletions src/neo-vm/ReferenceCounter.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Neo.VM.Collections;
using Neo.VM.Types;
using System.Collections.Generic;
using System.Linq;
Expand Down
23 changes: 5 additions & 18 deletions src/neo-vm/Types/Map.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Neo.VM.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
Expand All @@ -11,7 +12,7 @@ public class Map : CompoundType, IReadOnlyDictionary<PrimitiveType, StackItem>
{
public const int MaxKeySize = 64;

private readonly Dictionary<PrimitiveType, StackItem> dictionary;
private readonly OrderedDictionary<PrimitiveType, StackItem> dictionary = new OrderedDictionary<PrimitiveType, StackItem>();

public StackItem this[PrimitiveType key]
{
Expand Down Expand Up @@ -41,23 +42,9 @@ public StackItem this[PrimitiveType key]
internal override int SubItemsCount => dictionary.Count * 2;
public IEnumerable<StackItem> Values => dictionary.Values;

public Map(Dictionary<PrimitiveType, StackItem> value = null)
: this(null, value)
{
}

public Map(ReferenceCounter referenceCounter, Dictionary<PrimitiveType, StackItem> value = null)
public Map(ReferenceCounter referenceCounter = null)
: base(referenceCounter)
{
dictionary = value ?? new Dictionary<PrimitiveType, StackItem>();
if (referenceCounter != null)
foreach (var pair in dictionary)
{
if (pair.Key.GetByteLength() > MaxKeySize)
throw new ArgumentException();
referenceCounter.AddReference(pair.Key, this);
referenceCounter.AddReference(pair.Value, this);
}
}

public bool ContainsKey(PrimitiveType key)
Expand All @@ -67,12 +54,12 @@ public bool ContainsKey(PrimitiveType key)

IEnumerator<KeyValuePair<PrimitiveType, StackItem>> IEnumerable<KeyValuePair<PrimitiveType, StackItem>>.GetEnumerator()
{
return dictionary.GetEnumerator();
return ((IDictionary<PrimitiveType, StackItem>)dictionary).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return dictionary.GetEnumerator();
return ((IDictionary<PrimitiveType, StackItem>)dictionary).GetEnumerator();
}

public bool Remove(PrimitiveType key)
Expand Down
4 changes: 2 additions & 2 deletions tests/neo-vm.Tests/UtRandomAccessStack.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.VM.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.VM;

namespace Neo.Test
{
Expand Down
9 changes: 6 additions & 3 deletions tests/neo-vm.Tests/VMJsonTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;

namespace Neo.Test
Expand Down Expand Up @@ -78,20 +79,22 @@ private void AssertResult(VMUTExecutionEngineState result, ExecutionEngine engin
/// <param name="stack">Stack</param>
/// <param name="result">Result</param>
/// <param name="message">Message</param>
private void AssertResult(VMUTExecutionContextState[] result, RandomAccessStack<ExecutionContext> stack, string message)
private void AssertResult(VMUTExecutionContextState[] result, Stack<ExecutionContext> stack, string message)
{
AssertAreEqual(result == null ? 0 : result.Length, stack.Count, message + "Stack is different");

for (int x = 0, max = stack.Count; x < max; x++)
int x = 0;
foreach (var context in stack)
{
var context = stack.Peek(x);
var opcode = context.InstructionPointer >= context.Script.Length ? OpCode.RET : context.Script[context.InstructionPointer];

AssertAreEqual(result[x].NextInstruction, opcode, message + "Next instruction is different");
AssertAreEqual(result[x].InstructionPointer, context.InstructionPointer, message + "Instruction pointer is different");

AssertResult(result[x].EvaluationStack, context.EvaluationStack, message + " [EvaluationStack]");
AssertResult(result[x].AltStack, context.AltStack, message + " [AltStack]");

x++;
}
}

Expand Down

0 comments on commit be1903e

Please sign in to comment.