1 Commits

Author SHA1 Message Date
Smaug123
56fbf7e398 Expand exception logic 2025-07-02 22:50:34 +01:00
5 changed files with 170 additions and 43 deletions

View File

@@ -193,6 +193,24 @@ module TestPureCases =
NativeImpls = MockEnv.make () NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some 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" FileName = "Floats.cs"
ExpectedReturnCode = 0 ExpectedReturnCode = 0

View File

@@ -26,6 +26,8 @@
<EmbeddedResource Include="sourcesPure\Ldelema.cs" /> <EmbeddedResource Include="sourcesPure\Ldelema.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" /> <EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" /> <EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNestedHandlers.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithTypeMatching.cs" />
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" /> <EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />
<EmbeddedResource Include="sourcesPure\ComplexTryCatch.cs" /> <EmbeddedResource Include="sourcesPure\ComplexTryCatch.cs" />
<EmbeddedResource Include="sourcesPure\TriangleNumber.cs" /> <EmbeddedResource Include="sourcesPure\TriangleNumber.cs" />

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,32 +1,29 @@
namespace WoofWare.PawPrint namespace WoofWare.PawPrint
open System
open System.Collections.Immutable open System.Collections.Immutable
/// Represents a location in the code where an exception occurred /// Represents a location in the code where an exception occurred
type ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar type ExceptionStackFrame =
when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> =
{ {
Method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGen, 'methodVar> Method : WoofWare.PawPrint.MethodInfo<TypeDefn, TypeDefn>
/// The number of bytes into the IL of the method we were in /// The number of bytes into the IL of the method we were in
IlOffset : int IlOffset : int
} }
/// Represents a CLI exception being propagated /// 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 /// The exception object allocated on the heap
ExceptionObject : ManagedHeapAddress ExceptionObject : ManagedHeapAddress
/// Stack trace built during unwinding /// Stack trace built during unwinding
StackTrace : ExceptionStackFrame<'typeGen, 'methodGen, 'methodVar> list StackTrace : ExceptionStackFrame list
} }
/// Represents what to do after executing a finally/filter block /// Represents what to do after executing a finally/filter block
type ExceptionContinuation<'typeGen, 'methodGen, 'methodVar type ExceptionContinuation =
when 'typeGen : comparison and 'typeGen :> IComparable<'typeGen>> =
| ResumeAfterFinally of targetPC : int | ResumeAfterFinally of targetPC : int
| PropagatingException of exn : CliException<'typeGen, 'methodGen, 'methodVar> | PropagatingException of exn : CliException
| ResumeAfterFilter of handlerPC : int * exn : CliException<'typeGen, 'methodGen, 'methodVar> | ResumeAfterFilter of handlerPC : int * exn : CliException
/// Helper functions for exception handling /// Helper functions for exception handling
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
@@ -47,7 +44,7 @@ module ExceptionHandling =
let findExceptionHandler let findExceptionHandler
(currentPC : int) (currentPC : int)
(exceptionTypeCrate : TypeInfoCrate) (exceptionTypeCrate : TypeInfoCrate)
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>) (method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>) (assemblies : ImmutableDictionary<string, DumpedAssembly>)
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally : (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
= =
@@ -56,6 +53,7 @@ module ExceptionHandling =
| Some instructions -> | Some instructions ->
// Find all handlers that cover the current PC // Find all handlers that cover the current PC
let handlers =
instructions.ExceptionRegions instructions.ExceptionRegions
|> Seq.choose (fun region -> |> Seq.choose (fun region ->
match region with match region with
@@ -63,7 +61,7 @@ module ExceptionHandling =
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
// Check if exception type matches // Check if exception type matches
if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then
Some (region, false) Some (region, false, offset.TryOffset, offset.TryLength)
else else
None None
else else
@@ -73,29 +71,50 @@ module ExceptionHandling =
failwith "TODO: filter needs to be evaluated" failwith "TODO: filter needs to be evaluated"
else else
None None
| ExceptionRegion.Finally offset ->
// Don't return finally blocks here - they're handled separately
None
| ExceptionRegion.Fault offset ->
// Fault blocks are only executed when propagating exceptions
None
)
|> 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 -> | ExceptionRegion.Finally offset ->
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
Some (region, true) Some (region, true, offset.TryOffset, offset.TryLength)
else else
None None
| ExceptionRegion.Fault offset -> | ExceptionRegion.Fault offset ->
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
Some (region, true) Some (region, true, offset.TryOffset, offset.TryLength)
else else
None None
| _ -> None
) )
|> Seq.toList |> Seq.sortByDescending (fun (_, _, tryOffset, _) -> tryOffset)
|> fun x -> |> Seq.tryHead
match x with |> Option.map (fun (region, isFinally, _, _) -> (region, isFinally))
| [] -> None | handlers ->
| [ x ] -> Some x // Return the innermost handler (highest TryOffset, or smallest TryLength for same offset)
| _ -> failwith "multiple exception regions" 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 /// Find finally blocks that need to run when leaving a try region
let findFinallyBlocksToRun let findFinallyBlocksToRun
(currentPC : int) (currentPC : int)
(targetPC : int) (targetPC : int)
(method : WoofWare.PawPrint.MethodInfo<'typeGeneric, 'methodGeneric, 'methodVar>) (method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
: ExceptionOffset list : ExceptionOffset list
= =
match method.Instructions with match method.Instructions with
@@ -125,7 +144,7 @@ module ExceptionHandling =
/// Get the active exception regions at a given offset /// Get the active exception regions at a given offset
let getActiveRegionsAtOffset let getActiveRegionsAtOffset
(offset : int) (offset : int)
(method : WoofWare.PawPrint.MethodInfo<'a, 'b, 'c>) (method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
: WoofWare.PawPrint.ExceptionRegion list : WoofWare.PawPrint.ExceptionRegion list
= =
match method.Instructions with match method.Instructions with