Summary
Enum's in C# are not strict when it comes to casting. e.g.
```C#
public enum VMState : byte
{
NONE = 0,
HALT = 1 << 0,
FAULT = 1 << 1,
BREAK = 1 << 2,
}
VMState state = (VMState) 8; <- works fine
This can lead to various issues or performance degradation;
* In NEO2 a contract was successfully deployed with an invalid `ContractParameterType` (#228). The impact for this specific case was low but could be worse.
* Objects can pass deserialization with invalid data ([example](https://github.com/neo-project/neo/blob/f1d643430966b9cc13f66ecb4ef2cfb462ccee85/neo/Ledger/TransactionState.cs#L32)) leading to unnecessary resource consumption, where we could have thrown an exception, aborted early and save resources.
* The VM can run longer than necessary with invalid data ([interop layer example that could abort early](https://github.com/neo-project/neo/blob/f1d643430966b9cc13f66ecb4ef2cfb462ccee85/neo/SmartContract/InteropService.cs#L576))
**Do you have any solution you want to propose?**
I'd like to suggest we create some helper method that does strict Enum value checking and throws an exception if it is out of bounds.
So the above could look something like
```C#
VMState state = Helper.StrictEnumFrom<VMState>(8); <- raises FormatException
Where in the software does this update applies to?
Everywhere
I'm interested to hear thoughts on the idea
The implementation could be something along the lines of this (not working, but for showing the idea)
```C#
public static class EnumHelper
{
public static T StrictEnumFrom
{
T state = (T)value;
if (!EnumValueCache
throw new FormatException();
return state;
}
private static class EnumValueCache<T> where T : Enum
{
public static readonly HashSet<T> DefinedValues = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
}
}
```
Enum.IsDefined() is apparently slow (according to SO posts), so the cache should help avoid a performance hit every time we call Enum.GetValues for the type.
I like it, only when the enum is not a flag.
Sometimes we do this check
Sometimes we do this check
It is great that we at least do it sometimes, but I hope we can get to some coding rule where we say "Every non-flag enum in NEO should be strict" and then to make it easy have some helper function to take care of it.
@shargon is there any reason why we couldn't make an equivalent helper for Flag enums? I still kind of expect that an enum also does not accept any invalid flags. For example I expect this to fail if GetBigInteger() returns any value that is not 0 or 1 (=the valid possible Flags)
https://github.com/neo-project/neo/blob/f1d643430966b9cc13f66ecb4ef2cfb462ccee85/neo/SmartContract/InteropService.cs#L576
PS: If you happen to know a solution for T state = (T)value; not compiling let me know. T state = (T)(object)value; allows it to compile, but then it still fails at runtime so 馃し鈥嶁檪
If is fast, we can do it.. otherwise what could be the problem with flag, every time you will check the expected flag, so if is not included you won't do anything
Could you create the PR for see easy your problem?
If is fast, we can do it.. otherwise what could be the problem with flag, every time you will check the expected flag, so if is not included you won't do anything
I guess the only benefit would be saving computational resources by aborting VM execution on invalid data.
Could you create the PR for see easy your problem?
I can't really create a PR yet because the helper function doesn't work (see the last 2 lines of https://github.com/neo-project/neo/issues/1231#issuecomment-552848602)
Otherwise the PR would just become applying
C#
if (!Enum.IsDefined(typeof(object_type), Type))
throw new FormatException();
to all locations that are not using it but should.
Try with
public static class EnumHelper
{
public static T StrictEnumFrom<T>(int value) where T : Enum
{
T state = (T)Enum.ToObject(typeof(T), value);
if (!EnumValueCache<T>.DefinedValues.Contains(state))
throw new FormatException();
return state;
}
public static T StrictEnumFrom<T>(byte value) where T : Enum
{
T state = (T)Enum.ToObject(typeof(T), value);
if (!EnumValueCache<T>.DefinedValues.Contains(state))
throw new FormatException();
return state;
}
private static class EnumValueCache<T> where T : Enum
{
public static readonly HashSet<T> DefinedValues = new HashSet<T>((T[])Enum.GetValues(typeof(T)));
}
}
Please do some benchmarks with Enum.IsDefined(typeof(T), value); instead of your hashset
Don't mind my C# benchmark skills 馃槄
```C#
private static void func1(int i)
{
VMState state = EnumHelper.StrictFrom
}
private static void func2(int i)
{
VMState value = (VMState)i;
if (!Enum.IsDefined(typeof(VMState), value))
throw new FormatException();
}
```C#
var watch = new Stopwatch();
List<int> indice = new List<int> { 0, 1, 2, 4 };
// clean up
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
watch.Start();
for (int i = 0; i < 5000; i++)
{
func2(indice[i%4]);
}
watch.Stop();
Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
3 runs of the above code
func1: 35.639 ms, 33.4553 ms, 33.4517 ms
func2: 74.522 ms, 76.22 ms, 66.9185 ms
So the hashset is ~2x faster
Any update @ixje ?
I think that it's a good idea
I'll try to PR this week. First have to finish some other task.
@shargon I wanted to implement it in NEO but I'm getting the error:
Feature 'enum generic type constraints' is not available in C# 7.0. Please use language version 7.3 or greater
So this means the whole NEO project needs to be upgraded to use C# language version 7.3 or greater. This sounds like a big change, so before I continue I'd like to hear if that is acceptable.
In master we are using the preview version... it's weird
https://github.com/neo-project/neo/blob/8ccc5e93d82d534dafa2ff8dd751b9817f80d09b/neo/neo.csproj#L20
Most helpful comment
Don't mind my C# benchmark skills 馃槄
```C#(i);
private static void func1(int i)
{
VMState state = EnumHelper.StrictFrom
}
3 runs of the above code
func1: 35.639 ms, 33.4553 ms, 33.4517 ms
func2: 74.522 ms, 76.22 ms, 66.9185 ms
So the hashset is ~2x faster