From 56fbf7e3985cffeb928049670161735318754444 Mon Sep 17 00:00:00 2001 From: Smaug123 <3138005+Smaug123@users.noreply.github.com> Date: Wed, 2 Jul 2025 22:50:34 +0100 Subject: [PATCH] Expand exception logic --- WoofWare.PawPrint.Test/TestPureCases.fs | 18 +++ .../WoofWare.PawPrint.Test.fsproj | 2 + .../ExceptionWithNestedHandlers.cs | 47 ++++++++ .../sourcesPure/ExceptionWithTypeMatching.cs | 41 +++++++ WoofWare.PawPrint/Exceptions.fs | 105 +++++++++++------- 5 files changed, 170 insertions(+), 43 deletions(-) create mode 100644 WoofWare.PawPrint.Test/sourcesPure/ExceptionWithNestedHandlers.cs create mode 100644 WoofWare.PawPrint.Test/sourcesPure/ExceptionWithTypeMatching.cs diff --git a/WoofWare.PawPrint.Test/TestPureCases.fs b/WoofWare.PawPrint.Test/TestPureCases.fs index 48c7027..30f7b3b 100644 --- a/WoofWare.PawPrint.Test/TestPureCases.fs +++ b/WoofWare.PawPrint.Test/TestPureCases.fs @@ -193,6 +193,24 @@ module TestPureCases = NativeImpls = MockEnv.make () LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some } + { + FileName = "ExceptionWithNestedHandlers.cs" + ExpectedReturnCode = 10112 + NativeImpls = NativeImpls.PassThru () + LocalVariablesOfMain = + [ 10112 ] + |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) + |> Some + } + { + FileName = "ExceptionWithTypeMatching.cs" + ExpectedReturnCode = 10112 + NativeImpls = NativeImpls.PassThru () + LocalVariablesOfMain = + [ 10112 ] + |> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i)) + |> Some + } { FileName = "Floats.cs" ExpectedReturnCode = 0 diff --git a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj index a180a7f..d0f6ca4 100644 --- a/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj +++ b/WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj @@ -26,6 +26,8 @@ + + diff --git a/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithNestedHandlers.cs b/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithNestedHandlers.cs new file mode 100644 index 0000000..42033a6 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithNestedHandlers.cs @@ -0,0 +1,47 @@ +using System; + +namespace HelloWorld +{ + class Program { + static int Test_NestedHandlers() + { + int x = 0; + try + { + try + { + x = 1; + throw new InvalidOperationException(); + } + catch (InvalidOperationException) + { + x = 2; + throw new ArgumentException(); // Re-throw different exception + } + finally + { + x += 10; // Inner finally + } + } + catch (ArgumentException) + { + x += 100; // Outer catch + } + catch (Exception) + { + x += 1000; // This should NOT execute + } + finally + { + x += 10000; // Outer finally + } + + return x; // Expected: 10112 (1 -> 2 -> 12 -> 112 -> 10112) + } + + static int Main(string[] args) + { + return Test_NestedHandlers(); + } + } +} diff --git a/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithTypeMatching.cs b/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithTypeMatching.cs new file mode 100644 index 0000000..fed5ea7 --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/ExceptionWithTypeMatching.cs @@ -0,0 +1,41 @@ +using System; + +namespace HelloWorld +{ + class Program { + static int Test_TypeMatching() + { + int x = 0; + try + { + try + { + x = 1; + throw new ArgumentNullException(); // Derives from ArgumentException + } + catch (InvalidOperationException) + { + x = 999; // Should NOT execute + } + catch (ArgumentException) // This should catch ArgumentNullException + { + x = 2; + } + catch (Exception) + { + x = 888; // Should NOT execute (more general handler) + } + } + catch (Exception) + { + x = 777; // Should NOT execute (outer handler) + } + return x; // Expected: 2 + } + + static int Main(string[] args) + { + return Test_TypeMatching(); + } + } +} diff --git a/WoofWare.PawPrint/Exceptions.fs b/WoofWare.PawPrint/Exceptions.fs index bc765ee..4521cd3 100644 --- a/WoofWare.PawPrint/Exceptions.fs +++ b/WoofWare.PawPrint/Exceptions.fs @@ -1,32 +1,29 @@ namespace WoofWare.PawPrint -open System open System.Collections.Immutable /// Represents a location in the code where an exception occurred -type ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar - when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> = +type ExceptionStackFrame = { - Method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGen, 'methodVar> + Method : WoofWare.PawPrint.MethodInfo /// The number of bytes into the IL of the method we were in IlOffset : int } /// Represents a CLI exception being propagated -type CliException<'typeGen, 'methodGen, 'methodVar when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> = +type CliException = { /// The exception object allocated on the heap ExceptionObject : ManagedHeapAddress /// Stack trace built during unwinding - StackTrace : ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar> list + StackTrace : ExceptionStackFrame list } /// Represents what to do after executing a finally/filter block -type ExceptionContinuation<'typeGen, 'methodGen, 'methodVar - when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> = +type ExceptionContinuation = | ResumeAfterFinally of targetPC : int - | PropagatingException of exn : CliException<'typeGen, 'methodGen, 'methodVar> - | ResumeAfterFilter of handlerPC : int * exn : CliException<'typeGen, 'methodGen, 'methodVar> + | PropagatingException of exn : CliException + | ResumeAfterFilter of handlerPC : int * exn : CliException /// Helper functions for exception handling [] @@ -47,7 +44,7 @@ module ExceptionHandling = let findExceptionHandler (currentPC : int) (exceptionTypeCrate : TypeInfoCrate) - (method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>) + (method : WoofWare.PawPrint.MethodInfo) (assemblies : ImmutableDictionary) : (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally = @@ -56,46 +53,68 @@ module ExceptionHandling = | Some instructions -> // Find all handlers that cover the current PC - instructions.ExceptionRegions - |> Seq.choose (fun region -> - match region with - | ExceptionRegion.Catch (typeToken, offset) -> - if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then - // Check if exception type matches - if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then - Some (region, false) + let handlers = + instructions.ExceptionRegions + |> Seq.choose (fun region -> + match region with + | ExceptionRegion.Catch (typeToken, offset) -> + if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then + // Check if exception type matches + if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then + Some (region, false, offset.TryOffset, offset.TryLength) + else + None else None - else + | ExceptionRegion.Filter (filterOffset, offset) -> + if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then + failwith "TODO: filter needs to be evaluated" + else + None + | ExceptionRegion.Finally offset -> + // Don't return finally blocks here - they're handled separately None - | ExceptionRegion.Filter (filterOffset, offset) -> - if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then - failwith "TODO: filter needs to be evaluated" - else + | ExceptionRegion.Fault offset -> + // Fault blocks are only executed when propagating exceptions None - | ExceptionRegion.Finally offset -> - if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then - Some (region, true) - else - None - | ExceptionRegion.Fault offset -> - if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then - Some (region, true) - else - None - ) - |> Seq.toList - |> fun x -> - match x with - | [] -> None - | [ x ] -> Some x - | _ -> failwith "multiple exception regions" + ) + |> Seq.toList + + // If multiple catch handlers, return the innermost one (highest TryOffset) + match handlers with + | [] -> + // No catch/filter handler found, check for finally/fault blocks + // that need to run while propagating + instructions.ExceptionRegions + |> Seq.choose (fun region -> + match region with + | ExceptionRegion.Finally offset -> + if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then + Some (region, true, offset.TryOffset, offset.TryLength) + else + None + | ExceptionRegion.Fault offset -> + if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then + Some (region, true, offset.TryOffset, offset.TryLength) + else + None + | _ -> None + ) + |> Seq.sortByDescending (fun (_, _, tryOffset, _) -> tryOffset) + |> Seq.tryHead + |> Option.map (fun (region, isFinally, _, _) -> (region, isFinally)) + | handlers -> + // Return the innermost handler (highest TryOffset, or smallest TryLength for same offset) + handlers + |> List.sortBy (fun (_, _, tryOffset, tryLength) -> (-tryOffset, tryLength)) + |> List.head + |> fun (region, isFinally, _, _) -> Some (region, isFinally) /// Find finally blocks that need to run when leaving a try region let findFinallyBlocksToRun (currentPC : int) (targetPC : int) - (method : WoofWare.PawPrint.MethodInfo<'typeGeneric, 'methodGeneric, 'methodVar>) + (method : WoofWare.PawPrint.MethodInfo) : ExceptionOffset list = match method.Instructions with @@ -125,7 +144,7 @@ module ExceptionHandling = /// Get the active exception regions at a given offset let getActiveRegionsAtOffset (offset : int) - (method : WoofWare.PawPrint.MethodInfo<'a, 'b, 'c>) + (method : WoofWare.PawPrint.MethodInfo) : WoofWare.PawPrint.ExceptionRegion list = match method.Instructions with