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 ()
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

View File

@@ -26,6 +26,8 @@
<EmbeddedResource Include="sourcesPure\Ldelema.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNestedHandlers.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithTypeMatching.cs" />
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />
<EmbeddedResource Include="sourcesPure\ComplexTryCatch.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
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<TypeDefn, TypeDefn>
/// 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
[<RequireQualifiedAccess>]
@@ -47,7 +44,7 @@ module ExceptionHandling =
let findExceptionHandler
(currentPC : int)
(exceptionTypeCrate : TypeInfoCrate)
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>)
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, 'methodGeneric>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
: (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<TypeDefn, 'methodGeneric>)
: 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<TypeDefn, 'methodGeneric>)
: WoofWare.PawPrint.ExceptionRegion list
=
match method.Instructions with