diff --git a/WoofWare.PawPrint.Test/TestPureCases.fs b/WoofWare.PawPrint.Test/TestPureCases.fs index 92389da..2bff9d8 100644 --- a/WoofWare.PawPrint.Test/TestPureCases.fs +++ b/WoofWare.PawPrint.Test/TestPureCases.fs @@ -43,7 +43,7 @@ module TestPureCases = } { FileName = "ResizeArray.cs" - ExpectedReturnCode = 109 + ExpectedReturnCode = 114 NativeImpls = MockEnv.make () } { @@ -66,6 +66,11 @@ module TestPureCases = ExpectedReturnCode = 0 NativeImpls = MockEnv.make () } + { + FileName = "UnsafeAs.cs" + ExpectedReturnCode = 0 + NativeImpls = MockEnv.make () + } ] let cases : EndToEndTestCase list = @@ -178,11 +183,12 @@ module TestPureCases = use peImage = new MemoryStream (image) try + let realResult = RealRuntime.executeWithRealRuntime [||] image + realResult.ExitCode |> shouldEqual case.ExpectedReturnCode + let terminalState, terminatingThread = Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls [] - let realResult = RealRuntime.executeWithRealRuntime [||] image - let exitCode = match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with | [] -> failwith "expected program to return a value, but it returned void" @@ -193,7 +199,6 @@ module TestPureCases = exitCode |> shouldEqual realResult.ExitCode - exitCode |> shouldEqual case.ExpectedReturnCode with _ -> for message in messages () do System.Console.Error.WriteLine $"{message}" diff --git a/WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs b/WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs index d05854a..296cafd 100644 --- a/WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs +++ b/WoofWare.PawPrint.Test/sourcesPure/InitializeArray.cs @@ -8,7 +8,7 @@ namespace HelloWorldApp { int[] array = new[] { 1, 2, 3 }; - if (array.Sum() != 60) + if (array.Sum() != 6) { return 1; } diff --git a/WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs b/WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs index c99d0cb..4959a7d 100644 --- a/WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs +++ b/WoofWare.PawPrint.Test/sourcesPure/OverlappingStructs.cs @@ -191,7 +191,7 @@ public class StructLayoutTests static int TestStructPassing() { - var s = new SequentialStruct { A = 500, B = 50, C = 5000 }; + var s = new SequentialStruct { A = 500, B = 50, C = 5005 }; int result = ProcessSequential(s); if (result != 555) return 50; // 500 + 50 + 5 (C % 1000) diff --git a/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs b/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs new file mode 100644 index 0000000..8b1f177 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/UnsafeAs.cs @@ -0,0 +1,276 @@ +using System; +using System.Runtime.CompilerServices; + +public class TestUnsafeAs +{ + private struct Int32Wrapper + { + public int Value; + } + + private struct UInt32Wrapper + { + public uint Value; + } + + private struct TwoInt16s + { + public short First; + public short Second; + } + + private struct FourBytes + { + public byte B0; + public byte B1; + public byte B2; + public byte B3; + } + + private enum TestEnum : int + { + Value1 = 0x12345678, + Value2 = -1 + } + + // Test 1: Int32 -> UInt32 reinterpretation + public static int Test1() + { + int original = -1; + ref uint reinterpreted = ref Unsafe.As(ref original); + + if (reinterpreted != 0xFFFFFFFF) + return 1; + + reinterpreted = 0x12345678; + if (original != 0x12345678) + return 2; + + original = int.MinValue; + if (reinterpreted != 0x80000000) + return 3; + + return 0; + } + + // Test 2: Struct -> Struct reinterpretation + public static int Test2() + { + Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 }; + ref FourBytes bytes = ref Unsafe.As(ref wrapper); + + if (BitConverter.IsLittleEndian) + { + if (bytes.B0 != 0x04) return 10; + if (bytes.B1 != 0x03) return 11; + if (bytes.B2 != 0x02) return 12; + if (bytes.B3 != 0x01) return 13; + } + else + { + if (bytes.B0 != 0x01) return 14; + if (bytes.B1 != 0x02) return 15; + if (bytes.B2 != 0x03) return 16; + if (bytes.B3 != 0x04) return 17; + } + + bytes.B0 = 0xFF; + int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304); + if (wrapper.Value != expectedValue) + return 18; + + return 0; + } + + // Test 3: Int32 -> Two Int16s + public static int Test3() + { + int value = 0x12345678; + ref TwoInt16s halves = ref Unsafe.As(ref value); + + if (BitConverter.IsLittleEndian) + { + if (halves.First != unchecked((short)0x5678)) return 20; + if (halves.Second != 0x1234) return 21; + } + else + { + if (halves.First != 0x1234) return 22; + if (halves.Second != unchecked((short)0x5678)) return 23; + } + + halves.First = -1; + int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678); + if (value != expectedValue) + return 24; + + return 0; + } + + // Test 4: Array element reinterpretation + public static int Test4() + { + int[] intArray = new int[] { 0x01020304, 0x05060708 }; + ref uint uintRef = ref Unsafe.As(ref intArray[0]); + + if (uintRef != 0x01020304u) + return 30; + + uintRef = 0xAABBCCDD; + if (intArray[0] != unchecked((int)0xAABBCCDD)) + return 31; + if (intArray[1] != 0x05060708) + return 32; + + return 0; + } + + // Test 5: Bool -> Byte + public static int Test5() + { + bool trueValue = true; + bool falseValue = false; + + ref byte trueByte = ref Unsafe.As(ref trueValue); + ref byte falseByte = ref Unsafe.As(ref falseValue); + + if (trueByte != 1) + return 40; + if (falseByte != 0) + return 41; + + // Modify through byte reference + trueByte = 0; + if (trueValue != false) + return 42; + + falseByte = 1; + if (falseValue != true) + return 43; + + return 0; + } + + // Test 6: Char -> UInt16 + public static int Test6() + { + char ch = 'A'; + ref ushort asUInt16 = ref Unsafe.As(ref ch); + + if (asUInt16 != 65) + return 50; + + asUInt16 = 0x03B1; // Greek lowercase alpha + if (ch != 'α') + return 51; + + return 0; + } + + // Test 7: Float -> Int32 + public static int Test7() + { + float floatValue = 1.0f; + ref int intBits = ref Unsafe.As(ref floatValue); + + // IEEE 754: 1.0f = 0x3F800000 + if (intBits != 0x3F800000) + return 60; + + intBits = 0x40000000; // 2.0f in IEEE 754 + if (floatValue != 2.0f) + return 61; + + floatValue = -0.0f; + if (intBits != unchecked((int)0x80000000)) + return 62; + + return 0; + } + + // Test 8: Double -> Int64 + public static int Test8() + { + double doubleValue = 1.0; + ref long longBits = ref Unsafe.As(ref doubleValue); + + // IEEE 754: 1.0 = 0x3FF0000000000000 + if (longBits != 0x3FF0000000000000L) + return 70; + + longBits = 0x4000000000000000L; // 2.0 in IEEE 754 + if (doubleValue != 2.0) + return 71; + + return 0; + } + + // Test 9: Enum -> Underlying type + public static int Test9() + { + TestEnum enumValue = TestEnum.Value1; + ref int underlying = ref Unsafe.As(ref enumValue); + + if (underlying != 0x12345678) + return 80; + + underlying = -1; + if (enumValue != TestEnum.Value2) + return 81; + + return 0; + } + + // Test 10: Local variable reinterpretation + public static int Test10() + { + int local = unchecked((int)0xDEADBEEF); + ref uint localAsUint = ref Unsafe.As(ref local); + + if (localAsUint != 0xDEADBEEF) + return 90; + + localAsUint = 0xCAFEBABE; + if (local != unchecked((int)0xCAFEBABE)) + return 91; + + return 0; + } + + public static int Main(string[] argv) + { + var result = Test1(); + if (result != 0) return result; + + result = Test2(); + if (result != 0) return result; + + result = Test3(); + if (result != 0) return result; + + result = Test4(); + if (result != 0) return result; + + result = Test5(); + if (result != 0) return result; + + result = Test6(); + if (result != 0) return result; + + result = Test7(); + if (result != 0) return result; + + result = Test8(); + if (result != 0) return result; + + result = Test9(); + if (result != 0) return result; + + result = Test10(); + if (result != 0) return result; + + // All tests passed + return 0; + } +} +