6

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 { }

Here, an exception of type A is thrown for which there is no handler. In the finally block, an exception of type B is thrown for which there is a handler. Normally, exceptions thrown in finally blocks win, but it's different for unhandled exceptions.

When debugging, the debugger stops execution when A is thrown, and does not allow the finally block to be executed.

When not debugging (running it stand-alone from a command prompt), a message is shown (printed, and a crash dialog) about an unhandled exception, but after that, "All done!" does get printed.

When adding a top-level exception handler that does nothing more than rethrow the caught exception, all is well: there are no unexpected messages, and "All done!" is printed.

I understand how this is happening: the determination of whether an exception has a handler happens before any finally blocks get executed. This is generally desirable, and the current behaviour makes sense. finally blocks should generally not be throwing exceptions anyway.

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

  • In the current function member, each try statement that encloses the throw point is examined. For each statement S, starting with the innermost try statement and ending with the outermost try statement, the following steps are evaluated:
    • If the try block of S encloses the throw point and if S has one or more catch clauses, the catch clauses are examined [...]
    • Otherwise, if the try block or a catch block of S encloses the throw point and if S has a finally block, control is transferred to the finally block. If the finally block throws another exception, processing of the current exception is terminated. Otherwise, when control reaches the end point of the finally block, processing of the current exception is continued.
  • If an exception handler was not located in the current function invocation, the function invocation is terminated, and one of the following occurs:
    • [...]
  • If the exception processing terminates all function member invocations in the current thread, indicating that the thread has no handler for the exception, then the thread is itself terminated. The impact of such termination is implementation-defined.

An exception isn't considered unhandled, according to my reading of the spec, until after all function invocations have been terminated, and function invocations aren't terminated until the finally handlers have executed.

Am I missing something here, or is Microsoft's implementation of C# inconsistent with their own specification?

Community
  • 1
  • 1
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/65219/discussion-on-question-by-hvd-changing-an-unhandled-exception-to-a-handled-one-i). – George Stocker Nov 19 '14 at 15:00
  • 3
    Wording is a bit clumsy, possibly intentionally. But the CLR spec leaves no doubt about it whatsoever, chapter I.12.4.2.5 makes it crystal-clear that finally blocks are only ever executed *after* it has searched and found a handler. – Hans Passant Nov 19 '14 at 15:53

2 Answers2

1

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

When exception A happens SEH tries to find the first handler able to handle your exception type and then starts running all finally blocks, unwinding to it, but based on SEH logic there are no such handlers, so it cries about unhandled exception before .NET can enforce its own rule.

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

The IL by itself 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

Vincent
  • 1,459
  • 15
  • 37
Pavel Krymets
  • 6,253
  • 1
  • 22
  • 35
  • Have you tried running the code as a standalone console application? On my machine, it prints "Unhandled Exception: A: Exception of type 'A' was thrown.", which is definitely not the expected behavior. – Jeroen Mostert Nov 19 '14 at 13:57
  • No, I get Windows error reporting dialog. If I say cancel, "All done!" is printed. Which version of .Net, Windows you're using? – Sriram Sakthivel Nov 19 '14 at 13:57
  • The IL looks like a straightforward translation of C# to CIL, but if the semantics of `try`/`catch`/`finally` in C# (according to the specification) do not match the semantics of `.try`/`catch`/`.finally` in CIL, then a C# compiler should not perform such a straightforward translation. That is an interesting point, though: I should check the CIL specification to see what that says. –  Nov 19 '14 at 14:41
  • I do think your answer is still missing some relevant details, but have decided to accept it nonetheless since it was a large step in helping me find a full answer. Thank you. –  Nov 21 '14 at 08:25
1

Pavel Krymets's answer has shown that the C# compiler fairly directly translates try/catch/finally to CIL .try/catch/finally, and Hans Passant's comment on my question points out where the CIL specification requires the current behaviour. So insofar as there is a problem, it is indeed a conflict between the C# compiler and the C# specification.

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

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

One of the major points of exception filters is that they run before any finally blocks to determine if an exception has any handler at all. The language specification has not yet been updated to cover this: the language specification included with the Visual Studio 2015 Preview is the old C# 5.0 language specification. However, it would be difficult if not completely impossible to specify the behaviour for that in such a way that it would to claim that finally blocks are executed before an exception is considered unhandled. Given that, I'd say it's fairly certain that not only is the current behaviour intentional, it's also fairly certain that the spec will be updated to match.

I'm accepting Pavel Krymets's answer, because even though it does not completely answer my question by itself, it is the largest step towards a complete answer.