diff --git a/DotNetTests/DotNetTests.sln b/DotNetTests/DotNetTests.sln index 071db28..ca7d692 100644 --- a/DotNetTests/DotNetTests.sln +++ b/DotNetTests/DotNetTests.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32616.157 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestConsoleApp1", "TestConsoleApp1\TestConsoleApp1.csproj", "{5D532D6A-6C85-43B9-A563-4438E61FAE9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsoleApp1", "TestConsoleApp1\TestConsoleApp1.csproj", "{5D532D6A-6C85-43B9-A563-4438E61FAE9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppSharpTest", "CppSharpTest\CppSharpTest.csproj", "{F35FBA80-045A-4E1B-84D9-1227EA741E93}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {5D532D6A-6C85-43B9-A563-4438E61FAE9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D532D6A-6C85-43B9-A563-4438E61FAE9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D532D6A-6C85-43B9-A563-4438E61FAE9C}.Release|Any CPU.Build.0 = Release|Any CPU + {F35FBA80-045A-4E1B-84D9-1227EA741E93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F35FBA80-045A-4E1B-84D9-1227EA741E93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F35FBA80-045A-4E1B-84D9-1227EA741E93}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {F35FBA80-045A-4E1B-84D9-1227EA741E93}.Release|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DotNetTests/TestConsoleApp1/BinaryTextConverter.cs b/DotNetTests/TestConsoleApp1/BinaryTextConverter.cs new file mode 100644 index 0000000..5e0e790 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/BinaryTextConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace TestConsoleApp1 +{ + internal class BinaryTextConverter + { + public static int Main_(string[] args) + { + var input = +"11000010 10111111 01010001 01110101 01101001 11000011 10101001 01101110 01100101 01110011 00100000 01110011 01101111 01101101 01101111 01110011 00111111 00100000 11000010 10111111 01010001 01110101 11000011 10101001 00100000 01101000 01100001 01100011 01100101 01101101 01101111 01110011 00100000 01100001 01110001 01110101 11000011 10101101 00111111 00100000 01000001 01101100 01100111 01101111 00100000 01100101 01110011 01110100 11000011 10100001 00100000 01110011 01110101 01100011 01100101 01100100 01101001 01100101 01101110 01100100 01101111 00101100 00100000 01101110 01101111 00100000 01110011 01100001 01100010 01100101 01101101 01101111 01110011 00100000 01101101 01110101 01111001 00100000 01100010 01101001 01100101 01101110 00100000 01110001 01110101 11000011 10101001 00101100 00100000 01101110 01101001 00100000 01110000 01101111 01110010 00100000 01110001 01110101 11000011 10101001 00101100 00100000 01110000 01100101 01110010 01101111 00100000 01110011 11000011 10101101 00100000 01110001 01110101 01100101 00100000 01101100 01101111 00100000 01110011 01100001 01100010 01110010 01100101 01101101 01101111 01110011 00100000 01101101 01110101 01111001 00100000 01110000 01110010 01101111 01101110 01110100 01101111 00101110 00100000 01010101 01101110 01100001 00100000 01110000 01110010 01100101 01100111 01110101 01101110 01110100 01100001 00111010 00100000 11000010 10111111 01110001 01110101 11000011 10101001 00100000 01110011 01110101 01100011 01100101 01100100 01100101 00100000 01110011 01101001 00100000 01100101 01101110 01110100 01110010 01100001 01110011 00100000 01100001 00100000 01101100 01100001 00100000 01110000 11000011 10100001 01100111 01101001 01101110 01100001 00100000 01100100 01100101 00100000 01001101 01100001 01101110 01100110 01110010 01100101 01100100 00111111 01010011 01110100 01100001 01111001 00100000 01010100 01110101 01101110 01100101 01100100 00101100 00100000 01100011 01101111 01101101 01101111 00100000 01100100 01101001 01100011 01100101 01101110 00100000 01100101 01101100 01101100 01101111 01110011"; + + foreach(var part in input.Split(' ')) + { + Console.Write(ConvertToChar(part)); + } + + string ConvertToChar(string s) + { + var hex = Convert.ToInt32(s, 2); + var c = (char)hex; + return c.ToString(); + } + + return 0; + } + } +} \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/Allocator.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/Allocator.cs new file mode 100644 index 0000000..6f86d82 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/Allocator.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +#if NET8_0_OR_GREATER + +#nullable enable +namespace TestConsoleApp1.FrozenSegments; + +public unsafe class BumpPointerNativeAllocator : IDisposable +{ + private readonly IntPtr _segment; + private readonly long _limit; + + private bool _isDisposed; + private long _start; + private long _address; + private long _allocated; + + List> _externalRefs = new List>(); + + + public BumpPointerNativeAllocator(nint size) + { + _isDisposed = false; + _address = (IntPtr)NativeMemory.AlignedAlloc((nuint)size, 8); + _start = _address; + _segment = RegisterFrozenSegment((IntPtr)_address, size); + + var segment = (Heap_segment*)_segment; + _allocated = _address; + segment->allocated = (nint)_allocated; + + _limit = _address + size; + } + + ~BumpPointerNativeAllocator() + { + Dispose(); + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + GC.SuppressFinalize(this); + UnregisterFrozenSegment(_segment); + NativeMemory.AlignedFree((void*)_start); + _address = 0; + } + + public long Available => _limit - _address; + + public long Alive => _externalRefs.Count(x => x.TryGetTarget(out _)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int SizeOf() + { + var mt = typeof(T).TypeHandle.Value; + var methodTable = *(MethodTable*)mt; + + return methodTable.BaseSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(object? obj) + { + if(obj is null) + { + return false; + } + + var ptr = *(nint*)&obj; + return _start <= ptr && ptr < _limit; + } + + public T AllocateObject() where T : class + { + var mt = typeof(T).TypeHandle.Value; + var methodTable = *(MethodTable*)mt; + + var ptr = ReserveMemory(methodTable.BaseSize, true); + + // Write the header + *ptr = 0; + ptr++; + + // Write the mt + *ptr = mt; + + return *(T*)&ptr; + } + + public ref T AllocateStruct() where T : struct + { + var mt = typeof(T).TypeHandle.Value; + var methodTable = *(MethodTable*)mt; + + var ptr = ReserveMemory(methodTable.BaseSize, true); + + return ref Unsafe.AsRef(ptr); + } + + public string AllocateString(ReadOnlySpan data) + { + var mt = typeof(string).TypeHandle.Value; + var methodTable = *(MethodTable*)mt; + + var size = methodTable.BaseSize + (data.Length + 1) * sizeof(char); + + // Align up the size + size = (size + IntPtr.Size - 1) & ~(IntPtr.Size - 1); + + var ptr = ReserveMemory(size, false); + + // Write the header + *ptr = 0; + ptr++; + + // Write the MT + *ptr = mt; + + var dataPtr = (byte*)(ptr + 1); + + // Write the length + *(int*)dataPtr = data.Length; + + // Write the chars + var destination = new Span(dataPtr + sizeof(int), data.Length + 1); + + data.CopyTo(destination); + destination[^1] = '\0'; + + var res = *(string*)&ptr; + + _externalRefs.Add(new WeakReference(res)); + + return res; + } + + public T[] AllocateArray(int length, bool clear = false) + { + var arrayMt = typeof(T[]).TypeHandle.Value; + var arrayMethodTable = *(MethodTable*)arrayMt; + + var arraySize = arrayMethodTable.BaseSize + length * arrayMethodTable.ComponentSize; + + var ptr = ReserveMemory(arraySize, clear); + + // Write the header + *ptr = 0; + ptr++; + + // Write the MT + *ptr = arrayMt; + + // Write the length + *(ptr + 1) = length; + + return (T[])*(Array*)&ptr; + } + + static ReflectionHelpers.FuncWrapper _register = new ReflectionHelpers.FuncWrapper("System.GC::_RegisterFrozenSegment(System.IntPtr,System.IntPtr)"); + private static IntPtr RegisterFrozenSegment(IntPtr sectionAddress, nint sectionSize) + { + return (IntPtr)typeof(GC).GetMethod("_RegisterFrozenSegment", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, [sectionAddress, sectionSize])!; + } + + static ReflectionHelpers.ActionWrapper _unregister = new ReflectionHelpers.ActionWrapper("System.GC::_UnregisterFrozenSegment(System.IntPtr)"); + private static void UnregisterFrozenSegment(IntPtr segment) + { + typeof(GC).GetMethod("_UnregisterFrozenSegment", BindingFlags.NonPublic | BindingFlags.Static)!.Invoke(null, [segment]); + } + + private nint* ReserveMemory(int size, bool clear) + { + ObjectDisposedException.ThrowIf(_address == 0, typeof(BumpPointerNativeAllocator)); + + if (_address + size > _limit) + { + throw new OutOfMemoryException(); + } + + var objectAddress = Interlocked.Add(ref _address, size); + + if (objectAddress > _limit) + { + throw new OutOfMemoryException(); + } + + if (clear) + { + NativeMemory.Clear((void*)objectAddress, (nuint)size); + } + + var segment = (Heap_segment*)_segment; + Interlocked.Add(ref _allocated, size); + segment->allocated = (nint)_allocated; + + + return (nint*)(objectAddress - size); + } + + + [StructLayout(LayoutKind.Sequential)] + public struct Heap_segment + { + public nint allocated; + public nint committed; + public nint reserved; + public nint used; + public nint mem; + } + + [StructLayout(LayoutKind.Explicit)] + public struct MethodTable + { + [FieldOffset(0)] + public ushort ComponentSize; + + [FieldOffset(4)] + public int BaseSize; + } +} + +#endif \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/AllocatorTest.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/AllocatorTest.cs new file mode 100644 index 0000000..3f36f6e --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/AllocatorTest.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TestConsoleApp1.TaintedObjects; + +namespace TestConsoleApp1.FrozenSegments; + +internal static class AllocatorTest +{ + const int reps = 10000; + + public static int Main_(string[] args) + { +#if NET8_0_OR_GREATER + var sw = new Stopwatch(); + sw.Start(); + + GC.Collect(); GC.WaitForPendingFinalizers(); + TestContextCreation(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestTainting(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestLookup(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestMixed(); + GC.Collect(); GC.WaitForPendingFinalizers(); + + sw.Stop(); + var elapsed = sw.Elapsed; + //Console.WriteLine($"---------------------------------"); + //Console.WriteLine($"TOTAL : {elapsed.TotalMicroseconds} us"); +#endif + return 0; + } + + private static void TestContextCreation() + { + var sw = new Stopwatch(); + sw.Start(); + for (int x = 0; x < reps / 100; x++) + { + using var taintedObjects = new StringTaintedObjects(); + + var str2 = "String 2"; + + if (taintedObjects.Get(str2) is not null) { throw new Exception($"str2 should not be tainted"); } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"CONTEXT : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestTainting() + { + var sw = new Stopwatch(); + using var taintedObjects = new StringTaintedObjects(); + + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + sw.Start(); + + for (int y = 0; y < reps / 10; y++) + { + var str = $"String - {y}"; + taintedObjects.Taint(str, ranges); + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"TAINTING : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestLookup() + { + var sw = new Stopwatch(); + using var taintedObjects = new StringTaintedObjects(); + + var str1 = "String 1"; + + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + str1 = taintedObjects.Taint(str1, ranges); + for (int y = 0; y < reps; y++) + { + var str = $"String - {y}"; + taintedObjects.Taint(str, ranges); + } + + sw.Start(); + + for (int y = 0; y < reps; y++) + { + if (taintedObjects.Get(str1) is null) { throw new Exception($"str1 should be tainted"); } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"LOOKUP : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestMixed() + { + var sw = new Stopwatch(); + sw.Start(); + for (int x = 0; x < reps / 100; x++) + { + using var taintedObjects = new StringTaintedObjects(); + + var str1 = "String 1"; + var str2 = "String 2"; + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + for (int y = 0; y < reps / 10; y++) + { + var str = $"String - {y}"; + str1 = taintedObjects.Taint(str, ranges); + } + + for (int z = 0; z < reps; z++) + { + if (taintedObjects.Get(str1) is null) { throw new Exception($"str1 should be tainted"); } + if (taintedObjects.Get(str2) is not null) { throw new Exception($"str2 should not be tainted"); } + } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"ALL : {elapsed.TotalMicroseconds / (double)reps} us"); + } + +} \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/Range.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/Range.cs new file mode 100644 index 0000000..44f52c7 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/Range.cs @@ -0,0 +1,156 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace TestConsoleApp1.FrozenSegments; + +internal readonly struct Range : IComparable +{ + public Range(int start, int length, object? source = null) + { + this.Start = start; + this.Length = length; + this.Source = source; + } + + public int Start { get; } + + public int Length { get; } + + public object? Source { get; } + + public bool IsEmpty() + { + return Length <= 0; + } + + public override int GetHashCode() + { + return Start + Length; + } + + public Range Shift(int offset) + { + if (offset == 0) + { + return this; + } + + return new Range(Start + offset, Length, Source); + } + + public int CompareTo([AllowNull] Range other) + { + return this.Start.CompareTo(other.Start); + } + + internal bool IsBefore(Range? range) + { + if (range == null) + { + return true; + } + + return IsBefore(range.Value); + } + + internal bool IsBefore(Range range) + { + int offset = Start - range.Start; + if (offset == 0) + { + return Length <= range.Length; // put smaller ranges first + } + + return offset < 0; + } + + internal bool Intersects(Range range) + { + return range.Start < (Start + Length) && (range.Start + range.Length > Start); + } + + internal bool Contains(Range range) + { + if (Start > range.Start) + { + return false; + } + + return (Start + Length) >= (range.Start + range.Length); + } + + internal List Remove(Range range) + { + if (!Intersects(range)) + { + return new List { this }; + } + else if (range.Contains(this)) + { + return new List(); + } + else + { + List res = new List(3); + if (range.Start > Start) + { + res.Add(new Range(Start, range.Start - Start, Source)); + } + + int end = Start + Length; + int rangeEnd = range.Start + range.Length; + if (rangeEnd < end) + { + res.Add(new Range(rangeEnd, (end - rangeEnd), Source)); + } + + return res; + } + } + + internal Range? Intersection(Range? range) + { + if (range == null) { return null; } + + return Intersection(range.Value); + } + + internal Range? Intersection(Range range) + { + if (Start == range.Start && Length == range.Length) + { + return this; + } + + Range lead, trail; + if (Start < range.Start) + { + lead = this; + trail = range; + } + else + { + lead = range; + trail = this; + } + + int start = Math.Max(lead.Start, trail.Start); + int end = Math.Min(lead.Start + lead.Length, trail.Start + trail.Length); + if (start >= end) + { + return null; + } + else + { + return new Range(start, end - start); + } + } +} diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/ReflectionHelpers.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/ReflectionHelpers.cs new file mode 100644 index 0000000..72ce416 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/ReflectionHelpers.cs @@ -0,0 +1,203 @@ + +using System.Reflection.Emit; +using System.Reflection; +using System; +using System.Linq; +using System.Text; + +namespace TestConsoleApp1.FrozenSegments; + +public class ReflectionHelpers +{ + static Func GetFunc(MethodInfo method) + { + Func res = null; + try + { + DynamicMethod dynMethod = new DynamicMethod(method.Name + "_dynMethod", typeof(TRes), new Type[] { typeof(T1), typeof(T2) }, method.DeclaringType, true); + ILGenerator il = dynMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldarg_1); + il.EmitCall(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method, null); + il.Emit(OpCodes.Ret); + res = (Func)dynMethod.CreateDelegate(typeof(Func)); + } + catch (Exception err) + { + Console.WriteLine(err.ToString()); + } + return res; + } + + static Action GetAction(MethodInfo method) + { + Action res = null; + try + { + DynamicMethod dynMethod = new DynamicMethod(method.Name + "_dynMethod", null, new Type[] { typeof(T1) }, method.DeclaringType, true); + ILGenerator il = dynMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.EmitCall(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method, null); + il.Emit(OpCodes.Ret); + res = (Action)dynMethod.CreateDelegate(typeof(Action)); + } + catch (Exception err) + { + Console.WriteLine(err.ToString()); + } + return res; + } + + abstract public class MethodWrapper + { + public string Name { get; } + public string ParamsSignature { get; } + public string MethodSignature { get => Name + ParamsSignature; } + public string AssemblyName { get; } + + bool initialized = false; + string typeName = null; + MethodInfo method = null; + ConstructorInfo ctor = null; + + protected bool IsCtor => Name == ".ctor"; + protected MethodWrapper(string methodSignature, string assemblyName = null) + { + int typeIndex = methodSignature.IndexOf("::"); + if (typeIndex >= 0) + { + this.typeName = methodSignature.Substring(0, typeIndex); + methodSignature = methodSignature.Substring(typeIndex + 2); + } + int paramsIndex = methodSignature.IndexOf('('); + Name = methodSignature.Substring(0, paramsIndex); + ParamsSignature = methodSignature.Substring(paramsIndex); + AssemblyName = assemblyName; + } + + protected MethodInfo ResolveMethod(object obj = null) + { + if (!initialized) + { + if (typeName == null && obj == null) { throw new ArgumentNullException("obj"); } + Type t = typeName != null ? GetType(typeName) : obj.GetType(); + if (t == null && obj != null) { t = obj.GetType(); } + SetMethod(t); + } + if (method == null) + { + // Logger.Trace("Execute -> Method {0}::{1} not found on", typeName, MethodSignature); + throw new MissingMethodException(typeName, MethodSignature); + } + return method; + } + + protected ConstructorInfo ResolveCtor() + { + if (!initialized) + { + if (typeName == null) { throw new ArgumentNullException("obj"); } + Type t = GetType(typeName); + SetCtor(t); + } + if (ctor == null) + { + // Logger.Trace("Execute -> Method {0}::{1} not found on", typeName, MethodSignature); + throw new MissingMethodException(typeName, MethodSignature); + } + return ctor; + } + + private void SetMethod(Type t) + { + if (t != null) + { + if (typeName == null) { typeName = t.ToString(); } + method = t.GetMethods().FirstOrDefault(m => IsMethod(m)); + if (method == null) + { + method = t.GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).FirstOrDefault(m => IsMethod(m)); + } + } + initialized = true; + } + private void SetCtor(Type t) + { + if (t != null) + { + if (typeName == null) { typeName = t.ToString(); } + ctor = t.GetConstructors().FirstOrDefault(m => IsMethod(m)); + if (ctor == null) + { + ctor = t.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(m => IsMethod(m)); + } + } + initialized = true; + } + + private Type GetType(string typeName) + { + if (typeName == null) { return null; } + + if (AssemblyName == null) + { + Type t = Type.GetType(typeName); + if (t != null) { return t; } + + string assemblyName = typeName; + int pos = assemblyName.LastIndexOf("."); + while (pos >= 0) + { + assemblyName = assemblyName.Substring(0, pos); + var res = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith(assemblyName)).Select(a => a.GetType(typeName)).FirstOrDefault(type => type != null); + if (res != null) { return res; } + pos = assemblyName.LastIndexOf("."); + } + } + else + { + return AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith(AssemblyName)).Select(a => a.GetType(typeName)).FirstOrDefault(type => type != null); + } + return null; + } + private bool IsMethod(MethodBase m) + { + if (m.Name != Name) { return false; } + var parameters = m.GetParameters().Select(p => p.ParameterType.ToString()).ToArray(); + string signature = string.Format("({0})", string.Join(",", parameters)); + return signature == ParamsSignature; + } + } + public class FuncWrapper : MethodWrapper + { + Func func = null; + public FuncWrapper(string methodSignature) : base(methodSignature) { } + + public TRes Invoke(T1 arg1, T2 arg2) + { + if (func == null) + { + func = GetFunc(ResolveMethod(arg1)); + } + + return func.Invoke(arg1, arg2); + } + } + + public class ActionWrapper : MethodWrapper + { + Action func = null; + public ActionWrapper(string methodSignature) : base(methodSignature) { } + + public void Invoke(T1 arg1) + { + if (func == null) + { + func = GetAction(ResolveMethod(arg1)); + } + + func.Invoke(arg1); + } + } + +} diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObject.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObject.cs new file mode 100644 index 0000000..3d6a853 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObject.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +#if NET8_0_OR_GREATER + +#nullable enable + +namespace TestConsoleApp1.FrozenSegments; + +internal class StringTaintedObject +{ + private string _value; + + internal void Init(string value, Range[] ranges) + { + _value = value; + Ranges = ranges; + } + + public object? Value => _value; + + public bool IsAlive => true; + + public Range[] Ranges { get; private set; } + +} + +#endif \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObjects.cs b/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObjects.cs new file mode 100644 index 0000000..a20fa29 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/FrozenSegments/StringTaintedObjects.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +#if NET8_0_OR_GREATER + +#nullable enable + +namespace TestConsoleApp1.FrozenSegments; + +internal unsafe class StringTaintedObjects : IDisposable +{ + internal BumpPointerNativeAllocator _allocator; + int taintedObjectSize; + + + public StringTaintedObjects() + { + _allocator = new BumpPointerNativeAllocator(1024 * 1024 * 5); //5MB + taintedObjectSize = _allocator.SizeOf(); + } + + public void Dispose() + { + _allocator.Dispose(); + } + + public string? Taint(string? stringToTaint, Range[] ranges) + { + if (stringToTaint is null) + { + return stringToTaint; + } + + // Alloc the Range[] + var rangesPtr = _allocator.AllocateArray(ranges.Length); + rangesPtr = ranges; + + //Alloc the StringTaintedObject + var stringTaintedObject = _allocator.AllocateObject(); + + //Alloc the actual string + var taintedString = _allocator.AllocateString(stringToTaint); + + stringTaintedObject.Init(taintedString, rangesPtr); + + return taintedString; + } + + public unsafe StringTaintedObject? Get(string? stringToFind) + { + if(!_allocator.Contains(stringToFind)) + { + return null; + } + + //Retrieve the StringTaintedObject + + var ptr = (nint*)&stringToFind; //Address of the string + var taintedObjectPtr = *ptr - taintedObjectSize; //Address of the StringTaintedObject + var taintedObject = (StringTaintedObject*)&taintedObjectPtr; + + return *taintedObject; + } +} + +#endif \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/HardcodedSecretsTest.cs b/DotNetTests/TestConsoleApp1/HardcodedSecretsTest.cs index d7d1583..fc25197 100644 --- a/DotNetTests/TestConsoleApp1/HardcodedSecretsTest.cs +++ b/DotNetTests/TestConsoleApp1/HardcodedSecretsTest.cs @@ -1,4 +1,5 @@ -using System; +#if NET5_0_OR_GREATER +using System; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; @@ -9,6 +10,9 @@ using System.ComponentModel.DataAnnotations; using System.Data; using System.Runtime.Serialization; +using System.IO; +using System.Linq; +using System.Collections.Generic; namespace TestconsoleApp1 // Note: actual namespace depends on the project name. { @@ -767,4 +771,5 @@ public static string Hex8_4_4_4_12() } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/MetadataReaderTest.cs b/DotNetTests/TestConsoleApp1/MetadataReaderTest.cs index 684d700..4d944ef 100644 --- a/DotNetTests/TestConsoleApp1/MetadataReaderTest.cs +++ b/DotNetTests/TestConsoleApp1/MetadataReaderTest.cs @@ -1,4 +1,6 @@ -using System; +#if NET5_0_OR_GREATER +using System; +using System.IO; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Reflection.PortableExecutable; @@ -59,4 +61,5 @@ static int Function2(string param1) return param1.Length; } } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/Program.cs b/DotNetTests/TestConsoleApp1/Program.cs index c16d448..ddc9673 100644 --- a/DotNetTests/TestConsoleApp1/Program.cs +++ b/DotNetTests/TestConsoleApp1/Program.cs @@ -1,18 +1,24 @@ using System; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Reflection.PortableExecutable; +using TestConsoleApp1; +using TestConsoleApp1.FrozenSegments; +using TestConsoleApp1.TaintedObjects; -namespace TestconsoleApp1 // Note: actual namespace depends on the project name. +namespace TestconsoleApp1 { - internal class Program - { - static int Main(string[] args) - { - return HardcodedSecretsTest.Main_(args); + internal class Program + { + static int Main(string[] args) + { + Console.WriteLine("Allocator"); + AllocatorTest.Main_(args); + Console.WriteLine(); + Console.WriteLine("*****************************"); + Console.WriteLine(); + Console.WriteLine("TaintedMap"); + TaintedObjectsTest.Main_(args); - return -1; - } - } -} \ No newline at end of file + return -1; + } + } +} diff --git a/DotNetTests/TestConsoleApp1/StringHashTest.cs b/DotNetTests/TestConsoleApp1/StringHashTest.cs new file mode 100644 index 0000000..2b63920 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/StringHashTest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace TestConsoleApp1 +{ + internal class StringHashTest + { + public static int Main_(string[] args) + { + for(int b = 0; b < 301; b++) + { + PrintHash(b.ToString()); + PrintHash(b.ToString()); + } + + return 0; + } + + static void PrintHash(string s) + { + Console.WriteLine($"{s} : {RuntimeHelpers.GetHashCode(s)} {s.GetHashCode()} "); + } + } +} \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/TaintedObjects/DefaultTaintedMap.cs b/DotNetTests/TestConsoleApp1/TaintedObjects/DefaultTaintedMap.cs new file mode 100644 index 0000000..d2f9c17 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/TaintedObjects/DefaultTaintedMap.cs @@ -0,0 +1,280 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using TestConsoleApp1.TaintedObjects; + +namespace TestConsoleApp1.TaintedObjects; + +internal interface ITaintedObject +{ + public object? Value { get; } + + bool IsAlive { get; } + + public ITaintedObject? Next { get; set; } + + public int PositiveHashCode { get; } +} + +internal interface ITaintedMap +{ + public void Put(ITaintedObject tainted); + + public ITaintedObject Get(object obj); + + public List GetListValues(); + + public int GetEstimatedSize(); +} + +internal class DefaultTaintedMap : ITaintedMap +{ + // Default capacity. It MUST be a power of 2. + public const int DefaultCapacity = 1 << 14; + // Default flat mode threshold. + public const int DefaultFlatModeThresold = 1 << 13; + // Bitmask to convert hashes to positive integers. + public const int PositiveMask = int.MaxValue; + // Periodicity of table purges, as number of put operations. It MUST be a power of two. + private const int PurgeCount = 1 << 6; + // Bitmask for fast modulo with PURGE_COUNT. + private const int PurgeMask = PurgeCount - 1; + // Map containing the tainted objects + private ConcurrentDictionary _map; + // Bitmask for fast modulo with table length. + private int _lengthMask = 0; + // Flag to ensure we do not run multiple purges concurrently. + private bool _isPurging = false; + private object _purgingLock = new(); + // Number of hash table entries. If the hash table switches to flat mode, it stops counting elements. + private int _entriesCount; + /* Number of elements in the hash table before switching to flat mode. */ + private int _flatModeThreshold; + + public DefaultTaintedMap() + { + _map = new ConcurrentDictionary(); + _lengthMask = DefaultCapacity - 1; + _flatModeThreshold = DefaultFlatModeThresold; + } + + /// + /// Gets a value indicating whether flat mode is enabled or not. Once this is set to true, it is not set to false again unless clear() is called. + /// The get accessor is only intended for testing purposes. + /// + public bool IsFlat { get; private set; } = false; + + /// + /// Returns the ITaintedObject for the given input object. + /// + /// The object that should be found in the map + /// The retrieved tainted object or null + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ITaintedObject? Get(object objectToFind) + { + if (objectToFind is null) + { + return null; + } + + _map.TryGetValue(IndexObject(objectToFind), out var entry); + + while (entry != null) + { + if (objectToFind == entry.Value) + { + return entry; + } + + entry = entry.Next; + } + + return null; + } + + /// + /// Put a new TaintedObject in the dictionary. + /// + /// Tainted object + public void Put(ITaintedObject entry) + { + if (entry is null || (entry.Value is null or "")) + { + return; + } + + var index = Index(entry.PositiveHashCode); + + if (!IsFlat) + { + // By default, add the new entry to the head of the chain. + // We do not control duplicate entries. + _map.TryGetValue(index, out var existingValue); + entry.Next = existingValue; + + // If there are two callers calling Put on the same map and the objects have the same index and we are not flat, + // then one of the ITaintedObjects could potentially be lost because of racing conditions. + // We assume that the corresponding lock mechanism benefits would not compensate the performance loss. + + // We only count the entries if we are not in flat mode + Interlocked.Increment(ref _entriesCount); + } + + // If we flipped to flat mode: + // - Always override elements ignoring chaining. + // - Stop updating the estimated size. + + _map[index] = entry; + + if ((entry.PositiveHashCode & PurgeMask) == 0) + { + Purge(); + } + } + + /// + /// Purge entries that have been garbage collected. Only one concurrent call to this method is + /// allowed, further concurrent calls will be ignored. + /// + internal void Purge() + { + // Ensure we enter only once concurrently. + lock (_purgingLock) + { + if (_isPurging) + { + return; + } + + _isPurging = true; + } + + try + { + // Remove GC'd entries. + var removedCount = RemoveDeadKeys(); + + if (!IsFlat) + { + // We only count the entries if we are not in flat mode + if (Interlocked.Add(ref _entriesCount, -removedCount) > _flatModeThreshold) + { + IsFlat = true; + } + } + } + finally + { + // Reset purging flag. + lock (_purgingLock) + { + _isPurging = false; + } + } + } + + private int RemoveDeadKeys() + { + var removed = 0; + List deadKeys = new(); + ITaintedObject? previous; + + foreach (var key in _map.Keys.ToArray()) + { + var current = _map[key]; + previous = null; + + while (current is not null) + { + if (!current.IsAlive) + { + if (previous is null) + { + // We can delete the map key + if (current.Next is null) + { + deadKeys.Add(key); + } + else + { + _map[key] = current.Next; + } + } + else + { + previous.Next = current.Next; + } + + current = current.Next; + removed++; + } + else + { + previous = current; + current = current.Next; + } + } + } + + foreach (var key in deadKeys) + { + _map.TryRemove(key, out _); + } + + return removed; + } + + public void Clear() + { + IsFlat = false; + Interlocked.Exchange(ref _entriesCount, 0); + _map.Clear(); + } + + private int IndexObject(object objectStored) + { + return Index(PositiveHashCode(objectStored.GetHashCode())); + } + + private int PositiveHashCode(int hash) + { + return hash & PositiveMask; + } + + public int Index(int hash) + { + return hash & _lengthMask; + } + + public int GetEstimatedSize() + { + return _entriesCount; + } + + // For testing only + public List GetListValues() + { + List list = new(); + + foreach (var value in _map.Values) + { + var copy = value; + while (copy != null) + { + list.Add(copy); + copy = copy.Next; + } + } + + return (list); + } +} diff --git a/DotNetTests/TestConsoleApp1/TaintedObjects/IastUtils.cs b/DotNetTests/TestConsoleApp1/TaintedObjects/IastUtils.cs new file mode 100644 index 0000000..60afee1 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/TaintedObjects/IastUtils.cs @@ -0,0 +1,111 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; + +namespace TestConsoleApp1.TaintedObjects; + +internal static class IastUtils +{ + // From: https://probablydance.com/2018/06/16/fibonacci-hashing-the-optimization-that-the-world-forgot-or-a-better-alternative-to-integer-modulo/ + // (Although, not sure how relevant it is in this case) + private const int GoldenRatio = 1618033987; + + private const int StartHash = 17; + + // Avoid infinite loops + private const int MaxDepth = 5; + + public static int GetHashCode(T value) + { + unchecked + { + return (StartHash * 23) + GetHash(value); + } + } + + public static int GetHashCode(T1 value1, T2 value2) + { + var hash = StartHash; + unchecked + { + hash = (hash * 23) + GetHash(value1); + hash = (hash * 23) + GetHash(value2); + } + + return hash; + } + + public static int GetHashCode(T1 value1, T2 value2, T3 value3) + { + var hash = StartHash; + unchecked + { + hash = (hash * 23) + GetHash(value1); + hash = (hash * 23) + GetHash(value2); + hash = (hash * 23) + GetHash(value3); + } + + return hash; + } + + private static int GetHash(T element) + { + var hash = + element switch + { + string s => s.GetStaticHashCode(), + byte b => b, + short s => s, + int i => i, + long l => (int)l ^ (int)(l >> 32), + _ => element?.GetHashCode() ?? 0 + }; + + return hash; + } + + public static int IdentityHashCode(object item) + { + return (item?.GetHashCode() ?? 0); + } + + public static Range[] GetRangesForString(string stringValue) + { + return new Range[] { new Range(0, stringValue.Length, null) }; + } + + internal static unsafe int GetStaticHashCode(this string? target) + { + if (target == null) + { + return -1; + } + + unchecked + { + fixed (char* charPtr = target) + { + var int32Length = target.Length / 2; + var intPtr = (int*)charPtr; + + var hash = StartHash; + for (var i = 0; i < int32Length; i++) + { + hash += intPtr[i] * GoldenRatio; + } + + if (target.Length % 2 != 0) + { + hash += ((int)charPtr[target.Length - 1]) * GoldenRatio; + } + + return hash; + } + } + } +} diff --git a/DotNetTests/TestConsoleApp1/TaintedObjects/Range.cs b/DotNetTests/TestConsoleApp1/TaintedObjects/Range.cs new file mode 100644 index 0000000..3042f84 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/TaintedObjects/Range.cs @@ -0,0 +1,156 @@ +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace TestConsoleApp1.TaintedObjects; + +internal readonly struct Range : IComparable +{ + public Range(int start, int length, object? source = null) + { + this.Start = start; + this.Length = length; + this.Source = source; + } + + public int Start { get; } + + public int Length { get; } + + public object? Source { get; } + + public bool IsEmpty() + { + return Length <= 0; + } + + public override int GetHashCode() + { + return Start + Length; + } + + public Range Shift(int offset) + { + if (offset == 0) + { + return this; + } + + return new Range(Start + offset, Length, Source); + } + + public int CompareTo([AllowNull] Range other) + { + return this.Start.CompareTo(other.Start); + } + + internal bool IsBefore(Range? range) + { + if (range == null) + { + return true; + } + + return IsBefore(range.Value); + } + + internal bool IsBefore(Range range) + { + int offset = Start - range.Start; + if (offset == 0) + { + return Length <= range.Length; // put smaller ranges first + } + + return offset < 0; + } + + internal bool Intersects(Range range) + { + return range.Start < (Start + Length) && (range.Start + range.Length > Start); + } + + internal bool Contains(Range range) + { + if (Start > range.Start) + { + return false; + } + + return (Start + Length) >= (range.Start + range.Length); + } + + internal List Remove(Range range) + { + if (!Intersects(range)) + { + return new List { this }; + } + else if (range.Contains(this)) + { + return new List(); + } + else + { + List res = new List(3); + if (range.Start > Start) + { + res.Add(new Range(Start, range.Start - Start, Source)); + } + + int end = Start + Length; + int rangeEnd = range.Start + range.Length; + if (rangeEnd < end) + { + res.Add(new Range(rangeEnd, (end - rangeEnd), Source)); + } + + return res; + } + } + + internal Range? Intersection(Range? range) + { + if (range == null) { return null; } + + return Intersection(range.Value); + } + + internal Range? Intersection(Range range) + { + if (Start == range.Start && Length == range.Length) + { + return this; + } + + Range lead, trail; + if (Start < range.Start) + { + lead = this; + trail = range; + } + else + { + lead = range; + trail = this; + } + + int start = Math.Max(lead.Start, trail.Start); + int end = Math.Min(lead.Start + lead.Length, trail.Start + trail.Length); + if (start >= end) + { + return null; + } + else + { + return new Range(start, end - start); + } + } +} diff --git a/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjects.cs b/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjects.cs new file mode 100644 index 0000000..3ab1e1c --- /dev/null +++ b/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjects.cs @@ -0,0 +1,113 @@ +using System; +using System.Runtime.CompilerServices; + + +#nullable enable + +namespace TestConsoleApp1.TaintedObjects; + +// +// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License. +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. +// + + +internal class TaintedObject : ITaintedObject +{ + private readonly WeakReference _weak; + + public TaintedObject(object value, Range[] ranges) + { + _weak = new WeakReference(value); + PositiveHashCode = IastUtils.IdentityHashCode(value) & DefaultTaintedMap.PositiveMask; + Ranges = ranges; + } + + public object? Value => _weak.Target; + + public bool IsAlive => _weak.IsAlive; + + public int PositiveHashCode { get; } + + public Range[] Ranges { get; set; } + + public ITaintedObject? Next { get; set; } +} + + +internal class TaintedObjects : IDisposable +{ + private static readonly bool _largeNumericCache = false; + private readonly ITaintedMap _map; + + static TaintedObjects() + { + // From Net 8.0 onwards first 300 digit strings are cached instead of first 10 + _largeNumericCache = object.ReferenceEquals(299.ToString(), 299.ToString()); + } + + public TaintedObjects() + { + _map = new DefaultTaintedMap(); + } + + public void Dispose() + { + } + + public bool TaintInputString(string stringToTaint) + { + if (!IsFiltered(stringToTaint)) + { + _map.Put(new TaintedObject(stringToTaint, IastUtils.GetRangesForString(stringToTaint))); + return true; + } + + bool IsFiltered(string arg) + { + // Try to bail out ASAP + if (string.IsNullOrEmpty(arg)) { return true; } + + // 0 - 9 are cached only + if (!_largeNumericCache) + { + return arg.Length == 1 && char.IsDigit(arg[0]); + } + + return arg.Length switch + { + > 3 => false, // Only 0 - 299 are cached + _ when !char.IsDigit(arg[0]) => false, // Not a number + > 1 when !char.IsDigit(arg[1]) => false, // Not a number + > 2 when !char.IsDigit(arg[2]) => false, // Not a number + > 2 when arg[0] - '0' >= 3 => false, // Bigger than 299 + _ => true + }; + } + + return false; + } + + public void Taint(object objectToTaint, Range[] ranges) + { + if (objectToTaint is not null) + { + var objectAsString = objectToTaint as string; + if (objectAsString is null || objectAsString != string.Empty) + { + _map.Put(new TaintedObject(objectToTaint, ranges)); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TaintedObject? Get(object objectToFind) + { + return _map.Get(objectToFind) as TaintedObject; + } + + public int GetEstimatedSize() + { + return _map.GetEstimatedSize(); + } +} diff --git a/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjectsTest.cs b/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjectsTest.cs new file mode 100644 index 0000000..dbd5ae7 --- /dev/null +++ b/DotNetTests/TestConsoleApp1/TaintedObjects/TaintedObjectsTest.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TestConsoleApp1.FrozenSegments; + +namespace TestConsoleApp1.TaintedObjects; + +internal static class TaintedObjectsTest +{ + const int reps = 10000; + + public static int Main_(string[] args) + { +#if NET8_0_OR_GREATER + var sw = new Stopwatch(); + sw.Start(); + + GC.Collect(); GC.WaitForPendingFinalizers(); + TestContextCreation(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestTainting(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestLookup(); + GC.Collect(); GC.WaitForPendingFinalizers(); + TestMixed(); + GC.Collect(); GC.WaitForPendingFinalizers(); + + sw.Stop(); + var elapsed = sw.Elapsed; + //Console.WriteLine($"---------------------------------"); + //Console.WriteLine($"TOTAL : {elapsed.TotalMicroseconds} us"); +#endif + return 0; + } + + private static void TestContextCreation() + { + var sw = new Stopwatch(); + sw.Start(); + for (int x = 0; x < reps / 100; x++) + { + using var taintedObjects = new TaintedObjects(); + + var str1 = "String 1"; + var str2 = "String 2"; + //Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + //taintedObjects.Taint(str1, ranges); + + if (taintedObjects.Get(str2) is not null) { throw new Exception($"str2 should not be tainted"); } + //if (taintedObjects.Get(str1) is null) { throw new Exception($"str2 should be tainted"); } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"CONTEXT : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestTainting() + { + var sw = new Stopwatch(); + using var taintedObjects = new TaintedObjects(); + + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + sw.Start(); + + for (int y = 0; y < reps / 10; y++) + { + var str = $"String - {y}"; + taintedObjects.Taint(str, ranges); + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"TAINTING : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestLookup() + { + var sw = new Stopwatch(); + using var taintedObjects = new TaintedObjects(); + + var str1 = "String 1"; + + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + taintedObjects.Taint(str1, ranges); + for (int y = 0; y < reps; y++) + { + var str = $"String - {y}"; + taintedObjects.Taint(str, ranges); + } + + sw.Start(); + + for (int y = 0; y < reps; y++) + { + if (taintedObjects.Get(str1) is null) { throw new Exception($"str1 should be tainted"); } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"LOOKUP : {elapsed.TotalMicroseconds / (double)reps} us"); + } + + private static void TestMixed() + { + var sw = new Stopwatch(); + sw.Start(); + for (int x = 0; x < reps / 100; x++) + { + using var taintedObjects = new TaintedObjects(); + + var str1 = "String 1"; + var str2 = "String 2"; + Range[] ranges = [new Range(2, 3), new Range(7, 1)]; + + for (int y = 0; y < reps / 10; y++) + { + str1 = $"String - {y}"; + taintedObjects.Taint(str1, ranges); + } + + for (int z = 0; z < reps; z++) + { + if (taintedObjects.Get(str1) is null) { throw new Exception($"str1 should be tainted"); } + if (taintedObjects.Get(str2) is not null) { throw new Exception($"str2 should not be tainted"); } + } + } + + sw.Stop(); + var elapsed = sw.Elapsed; + + Console.WriteLine($"---------------------------------"); + Console.WriteLine($"ALL : {elapsed.TotalMicroseconds / (double)reps} us"); + } +} \ No newline at end of file diff --git a/DotNetTests/TestConsoleApp1/TestConsoleApp1.csproj b/DotNetTests/TestConsoleApp1/TestConsoleApp1.csproj index 40c60dd..0ab8d89 100644 --- a/DotNetTests/TestConsoleApp1/TestConsoleApp1.csproj +++ b/DotNetTests/TestConsoleApp1/TestConsoleApp1.csproj @@ -2,9 +2,18 @@ Exe - net6.0 - enable - enable + + net8.0 + disable + x64 + + + + true + + + + true