Skip to content

Commit

Permalink
[Neo Core Bug] fix compound type reference issue (#3334)
Browse files Browse the repository at this point in the history
* fix compound type reference issue

* fix warning

* add benchmark

* throw exception instead

* Update benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

* Update src/Neo.VM/Types/Map.cs

* Apply suggestions from code review

* Update src/Neo.VM/Types/Map.cs

Co-authored-by: Shargon <[email protected]>

* update accessibality.

---------

Co-authored-by: Shargon <[email protected]>
Co-authored-by: NGD Admin <[email protected]>
  • Loading branch information
3 people committed Aug 6, 2024
1 parent bd820fb commit 1dcec2e
Show file tree
Hide file tree
Showing 9 changed files with 444 additions and 5 deletions.
122 changes: 122 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// Benchmarks.Types.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 BenchmarkDotNet.Attributes;
using Array = Neo.VM.Types.Array;

namespace Neo.VM.Benchmark;

public class Benchmarks_Types
{
public IEnumerable<(int Depth, int ElementsPerLevel)> ParamSource()
{
int[] depths = [2, 4];
int[] elementsPerLevel = [2, 4, 6];

foreach (var depth in depths)
{
foreach (var elements in elementsPerLevel)
{
if (depth <= 8 || elements <= 2)
{
yield return (depth, elements);
}
}
}
}

[ParamsSource(nameof(ParamSource))]
public (int Depth, int ElementsPerLevel) Params;

[Benchmark]
public void BenchNestedArrayDeepCopy()
{
var root = new Array(new ReferenceCounter());
CreateNestedArray(root, Params.Depth, Params.ElementsPerLevel);
_ = root.DeepCopy();
}

[Benchmark]
public void BenchNestedArrayDeepCopyWithReferenceCounter()
{
var referenceCounter = new ReferenceCounter();
var root = new Array(referenceCounter);
CreateNestedArray(root, Params.Depth, Params.ElementsPerLevel, referenceCounter);
_ = root.DeepCopy();
}

[Benchmark]
public void BenchNestedTestArrayDeepCopy()
{
var root = new TestArray(new ReferenceCounter());
CreateNestedTestArray(root, Params.Depth, Params.ElementsPerLevel);
_ = root.DeepCopy();
}

[Benchmark]
public void BenchNestedTestArrayDeepCopyWithReferenceCounter()
{
var referenceCounter = new ReferenceCounter();
var root = new TestArray(referenceCounter);
CreateNestedTestArray(root, Params.Depth, Params.ElementsPerLevel, referenceCounter);
_ = root.DeepCopy();
}

private static void CreateNestedArray(Array? rootArray, int depth, int elementsPerLevel = 1, ReferenceCounter? referenceCounter = null)
{
if (depth < 0)
{
throw new ArgumentException("Depth must be non-negative", nameof(depth));
}

if (rootArray == null)
{
throw new ArgumentNullException(nameof(rootArray));
}

if (depth == 0)
{
return;
}

for (var i = 0; i < elementsPerLevel; i++)
{
var childArray = new Array(referenceCounter);
rootArray.Add(childArray);
CreateNestedArray(childArray, depth - 1, elementsPerLevel, referenceCounter);
}
}

private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, ReferenceCounter referenceCounter = null)

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test-Everything

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test-Everything

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

Cannot convert null literal to non-nullable reference type.
{
if (depth < 0)
{
throw new ArgumentException("Depth must be non-negative", nameof(depth));
}

if (rootArray == null)
{
throw new ArgumentNullException(nameof(rootArray));
}

if (depth == 0)
{
return;
}

for (var i = 0; i < elementsPerLevel; i++)
{
var childArray = new TestArray(referenceCounter);
rootArray.Add(childArray);
CreateNestedTestArray(childArray, depth - 1, elementsPerLevel, referenceCounter);
}
}
}
141 changes: 141 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/TestArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// TestArray.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.Collections;

namespace Neo.VM.Benchmark;

public class TestArray : CompoundType, IReadOnlyList<StackItem>
{
protected readonly List<StackItem> _array;

/// <summary>
/// Get or set item in the array.
/// </summary>
/// <param name="index">The index of the item in the array.</param>
/// <returns>The item at the specified index.</returns>
public StackItem this[int index]
{
get => _array[index];
set
{
if (IsReadOnly) throw new InvalidOperationException("The object is readonly.");
ReferenceCounter?.RemoveReference(_array[index], this);
_array[index] = value;
ReferenceCounter?.AddReference(value, this);
}
}

/// <summary>
/// The number of items in the array.
/// </summary>
public override int Count => _array.Count;
public override IEnumerable<StackItem> SubItems => _array;
public override int SubItemsCount => _array.Count;
public override StackItemType Type => StackItemType.Array;

/// <summary>
/// Create an array containing the specified items.
/// </summary>
/// <param name="items">The items to be included in the array.</param>
public TestArray(IEnumerable<StackItem>? items = null)
: this(null, items)
{
}

/// <summary>
/// Create an array containing the specified items. And make the array use the specified <see cref="ReferenceCounter"/>.
/// </summary>
/// <param name="referenceCounter">The <see cref="ReferenceCounter"/> to be used by this array.</param>
/// <param name="items">The items to be included in the array.</param>
public TestArray(ReferenceCounter? referenceCounter, IEnumerable<StackItem>? items = null)
: base(referenceCounter)
{
_array = items switch
{
null => new List<StackItem>(),
List<StackItem> list => list,
_ => new List<StackItem>(items)
};
if (referenceCounter != null)
foreach (StackItem item in _array)
referenceCounter.AddReference(item, this);
}

/// <summary>
/// Add a new item at the end of the array.
/// </summary>
/// <param name="item">The item to be added.</param>
public void Add(StackItem item)
{
if (IsReadOnly) throw new InvalidOperationException("The object is readonly.");
_array.Add(item);
ReferenceCounter?.AddReference(item, this);
}

public override void Clear()
{
if (IsReadOnly) throw new InvalidOperationException("The object is readonly.");
if (ReferenceCounter != null)
foreach (StackItem item in _array)
ReferenceCounter.RemoveReference(item, this);
_array.Clear();
}

public override StackItem ConvertTo(StackItemType type)
{
if (Type == StackItemType.Array && type == StackItemType.Struct)
return new Struct(ReferenceCounter, new List<StackItem>(_array));
return base.ConvertTo(type);
}

internal sealed override StackItem DeepCopy(Dictionary<StackItem, StackItem> refMap, bool asImmutable)
{
if (refMap.TryGetValue(this, out StackItem? mappedItem)) return mappedItem;
var result = this is TestStruct ? new TestStruct(ReferenceCounter) : new TestArray(ReferenceCounter);
refMap.Add(this, result);
foreach (StackItem item in _array)
result.Add(item.DeepCopy(refMap, asImmutable));
result.IsReadOnly = true;
return result;
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

public IEnumerator<StackItem> GetEnumerator()
{
return _array.GetEnumerator();
}

/// <summary>
/// Remove the item at the specified index.
/// </summary>
/// <param name="index">The index of the item to be removed.</param>
public void RemoveAt(int index)
{
if (IsReadOnly) throw new InvalidOperationException("The object is readonly.");
ReferenceCounter?.RemoveReference(_array[index], this);
_array.RemoveAt(index);
}

/// <summary>
/// Reverse all items in the array.
/// </summary>
public void Reverse()
{
if (IsReadOnly) throw new InvalidOperationException("The object is readonly.");
_array.Reverse();
}
}
129 changes: 129 additions & 0 deletions benchmarks/Neo.VM.Benchmarks/TestStruct.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// TestStruct.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;

namespace Neo.VM.Benchmark;

public class TestStruct : TestArray
{
public override StackItemType Type => StackItemType.Struct;

/// <summary>
/// Create a structure with the specified fields.
/// </summary>
/// <param name="fields">The fields to be included in the structure.</param>
public TestStruct(IEnumerable<StackItem>? fields = null)
: this(null, fields)
{
}

/// <summary>
/// Create a structure with the specified fields. And make the structure use the specified <see cref="ReferenceCounter"/>.
/// </summary>
/// <param name="referenceCounter">The <see cref="ReferenceCounter"/> to be used by this structure.</param>
/// <param name="fields">The fields to be included in the structure.</param>
public TestStruct(ReferenceCounter? referenceCounter, IEnumerable<StackItem>? fields = null)
: base(referenceCounter, fields)
{
}

/// <summary>
/// Create a new structure with the same content as this structure. All nested structures will be copied by value.
/// </summary>
/// <param name="limits">Execution engine limits</param>
/// <returns>The copied structure.</returns>
public TestStruct Clone(ExecutionEngineLimits limits)
{
int count = (int)(limits.MaxStackSize - 1);
TestStruct result = new(ReferenceCounter);
Queue<TestStruct> queue = new();
queue.Enqueue(result);
queue.Enqueue(this);
while (queue.Count > 0)
{
TestStruct a = queue.Dequeue();
TestStruct b = queue.Dequeue();
foreach (StackItem item in b)
{
count--;
if (count < 0) throw new InvalidOperationException("Beyond clone limits!");
if (item is TestStruct sb)
{
TestStruct sa = new(ReferenceCounter);
a.Add(sa);
queue.Enqueue(sa);
queue.Enqueue(sb);
}
else
{
a.Add(item);
}
}
}
return result;
}

public override StackItem ConvertTo(StackItemType type)
{
if (type == StackItemType.Array)
return new TestArray(ReferenceCounter, new List<StackItem>(_array));
return base.ConvertTo(type);
}

public override bool Equals(StackItem? other)
{
throw new NotSupportedException();
}

internal override bool Equals(StackItem? other, ExecutionEngineLimits limits)
{
if (other is not TestStruct s) return false;
Stack<StackItem> stack1 = new();
Stack<StackItem> stack2 = new();
stack1.Push(this);
stack2.Push(s);
uint count = limits.MaxStackSize;
uint maxComparableSize = limits.MaxComparableSize;
while (stack1.Count > 0)
{
if (count-- == 0)
throw new InvalidOperationException("Too many struct items to compare.");
StackItem a = stack1.Pop();
StackItem b = stack2.Pop();
if (a is ByteString byteString)
{
if (!byteString.Equals(b, ref maxComparableSize)) return false;
}
else
{
if (maxComparableSize == 0)
throw new InvalidOperationException("The operand exceeds the maximum comparable size.");
maxComparableSize -= 1;
if (a is TestStruct sa)
{
if (ReferenceEquals(a, b)) continue;
if (b is not TestStruct sb) return false;
if (sa.Count != sb.Count) return false;
foreach (StackItem item in sa)
stack1.Push(item);
foreach (StackItem item in sb)
stack2.Push(item);
}
else
{
if (!a.Equals(b)) return false;
}
}
}
return true;
}
}
Loading

0 comments on commit 1dcec2e

Please sign in to comment.