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