Modifying an unhandled exception with processed in the final block

Consider this program:

using System; static class Program { static void Main(string[] args) { try { try { throw new A(); } finally { throw new B(); } } catch (B) { } Console.WriteLine("All done!"); } } class A : Exception { } class B : Exception { } 

An exception of type A is thrown for which there is no handler. In the finally block, an exception of type B selected for which there is a handler. Typically, exceptions thrown at finally block the gain, but it is different for unhandled exceptions.

When debugging, the debugger stops execution when A called and does not allow the execution of the finally block.

If debugging fails (it starts autonomously from the command line), a message is displayed (printed and failure dialog) about the unhandled exception, but after that "Everything is done!" prints out.

When adding a top-level exception handler that does nothing more than reconstruct the caught exception, everything is in order: there are no unexpected messages and "Everything is done!". is printed.

I understand how this happens: determining whether an exception is thrown by the handler before any finally blocks are executed. This is usually desirable, and current behavior makes sense. finally blocks should not generally throw exceptions.

But this other question refers to the C # language specification and states that the finally block is required to override exception A Reading the specification, I agree that this is exactly what it requires:

  • The current member of the function considers each try that spans the cast point. For each S statement, starting with the innermost try statement and ending with the outer try statement, the following steps are evaluated:
    • If a try block from S encloses a cast point, and if S has one or more catch , catch conditions are considered [...]
    • Otherwise, if a try or catch block of an S block encloses a cast point, and if S has a finally block, control is passed to the finally block. If the finally block throws another exception, the processing of the current exception is terminated. Otherwise, when control reaches the endpoint of the finally block, processing of the current exception continues.
  • If the exception handler was not in the current function call, the function call ends and one of the following events occurs:
    • [...]
  • If exception handling completes all calls to function members in the current thread, indicating that the thread does not have an exception handler, then the thread itself ends. The effect of such completion is determined by the implementation.

An exception is not considered unhandled according to my reading of the specification until all function calls are completed and function calls are completed until finally handlers are executed.

Am I missing something, or is the Microsoft C # implementation not up to their own specification?

+6
source share
2 answers

I think the problem is how .NET exception handling is built on top of Structured Exception Handling, which has slightly different rules about throwing at the finally block.

When exception A occurs, SEH tries to find the first handler that can handle your type of exception and then launches all finally blocks, unwinding it, but based on the SEH logic, there are no such handlers, so it screams for an unhandled exception before .NET can apply its own rule .

This explains the top-level handler (but only the one that can handle exception type A), fixing the problem.

IL per se looks valid:

 .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 49 (0x31) .maxstack 1 IL_0000: nop IL_0001: nop .try { IL_0002: nop .try { IL_0003: nop IL_0004: newobj instance void A::.ctor() IL_0009: throw } // end .try finally { IL_000a: nop IL_000b: newobj instance void B::.ctor() IL_0010: throw } // end handler } // end .try catch B { IL_0011: pop IL_0012: nop IL_0013: ldstr "B" IL_0018: call void [mscorlib]System.Console::WriteLine(string) IL_001d: nop IL_001e: nop IL_001f: leave.s IL_0021 } // end handler IL_0021: nop IL_0022: nop IL_0023: nop IL_0024: ldstr "A" IL_0029: call void [mscorlib]System.Console::WriteLine(string) IL_002e: nop IL_002f: nop IL_0030: ret } // end of method Program::Main 

Mono has the same problem http://ideone.com/VVoPx6

+1
source

Pavel Krymets answer showed that the C # compiler fairly translates try / catch / finally to CIL .try / catch / finally , and the Hans Passant comment on my question indicates where the CIL specification requires current behavior. Therefore, since there is a problem, this is really a conflict between the C # compiler and the C # specification.

Something I noticed is that the Roslyn compiler includes experimental new language functions, and one of these new language functions deals with try / catch : it supports exception filters with try / catch if syntax:

 try { ... } catch (Exception e) if (...) { ... } 

One of the main points of exception filters is that they are run before any finally blocks to determine if an exception has any exception handler at all. The language specification has not yet been updated to cover this: the language specification included in Visual Studio 2015 Preview is an old C # 5.0 language specification. However, it would be difficult if it were not completely impossible to specify the behavior for this in such a way that it states that the finally blocks are executed before the exception is considered unhandled. Given this, I would say that he is confident that not only the current behavior is intentional, but also quite confident that the specification will be updated to comply.

I accept the defendant Pavel Krymetts, because although he does not completely answer my question on his own, this is the biggest step towards a complete answer.

+1
source

Source: https://habr.com/ru/post/978399/


All Articles