Compiler generates infinite loop after finally block when
c#, cil, roslyn
Solution
This is by design. As long as you cannot reach that infinite loop :-) Thanks for reporting this issue though!!!
=== longer version:
When "finally" does not terminate (contains a throw or an infinite loop), the code after the try statement becomes unreachable form the language prospective. Since it is unreachable, it is allowed to have no code there whatsoever, even if, for example, the method needs to return a value. In fact, because various invariants, which typically hold in normal code are not enforced in unreachable code, compiler defensively removes code that is unreachable even if it is present. This is not just an optimization, it is often required for correctness. Instead of preventing/detecting/fixing violations in unreachable code it is cleaner to just remove it.
Now, IL specification requires that the "leave" opcode points to a valid target instruction. In particular it does not care whether the branch is blocked by a nonterminating finally. But we do not have any valid code to point to, so we need to inject a "landing" piece of code. It must be small. We also know that it would not be ever reachable, but it also must not endanger already established static correctness of the method.
An infinite loop is a smallest piece of code like that. BTW, another possibility could be "throw null", but historically an infinite loop is used.
No, NOP would not work because it would make the next instruction verifier-reachable, and that can result in violations of other IL rules like "do not drop through the end of the method, must use ret".
Problem
I'm using standard VS2015 compiler targeted for .Net 4.6.2. Compilator emits infinite loop after failing finally block. Some examples: Debug: ``` IL_0000: nop .try { IL_0001: nop IL_0002: nop IL_0003: leave.s IL_000c } // end .try finally { IL_0005: nop IL_0006: br.s IL_000a // loop start (head: IL_000a) IL_0008: nop IL_0009: nop IL_000a: br.s IL_0008 // end loop } // end handler // loop start (head: IL_000c) IL_000c: br.s IL_000c // end loop ``` Release: ``` .try { IL_0000: leave.s IL_0004 } // end .try finally { // loop start IL_0002: br.s IL_0002 // end loop } // end handler // loop start (head: IL_0004) IL_0004: br.s IL_0004 // end loop ``` Source C# code ``` private void _Simple() { try { } finally { for (;;) { } } } ``` As you see at IL_000c is infinite loop (generated by compilator) Ok, now I'll show you a bit extended case Debug: ``` IL_0000: nop .try { IL_0001: nop .try { IL_0002: nop IL_0003: nop IL_0004: leave.s IL_000d } // end .try finally { IL_0006: nop IL_0007: newobj instance void [mscorlib]System.Exception::.ctor() IL_000c: throw } // end handler // loop start (head: IL_000d) IL_000d: br.s IL_000d // end loop } // end .try finally { IL_000f: nop IL_0010: newobj instance void [mscorlib]System.Exception::.ctor() IL_0015: throw } // end handler ``` Release: ``` .try { .try { IL_0000: leave.s IL_0008 } // end .try finally { IL_0002: newobj instance void [mscorlib]System.Exception::.ctor() IL_0007: throw } // end handler // loop start (head: IL_0008) IL_0008: br.s IL_0008 // end loop } // end .try finally { IL_000a: newobj instance void [mscorlib]System.Exception::.ctor() IL_000f: throw } // end handler ``` After nested finally infinite loop is generated once again, but after second finally is not. (IL_000d) Source C# ``` private void _DoubleFinallyWithThrowingNewException() { try { try { } finally { throw new Exception(); } } finally { throw new Exception(); } } ``` One again, now there is non explicit exception thrown by method called at finally block. Debug: ``` IL_0000: nop .try { IL_0001: nop .try { IL_0002: nop IL_0003: nop IL_0004: leave.s IL_0010 } // end .try finally { IL_0006: nop IL_0007: ldarg.0 IL_0008: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_000d: nop IL_000e: nop IL_000f: endfinally } // end handler IL_0010: nop IL_0011: leave.s IL_001d } // end .try finally { IL_0013: nop IL_0014: ldarg.0 IL_0015: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_001a: nop IL_001b: nop IL_001c: endfinally } // end handler IL_001d: ret ``` Release: ``` .try { .try { IL_0000: leave.s IL_0010 } // end .try finally { IL_0002: ldarg.0 IL_0003: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_0008: endfinally } // end handler } // end .try finally { IL_0009: ldarg.0 IL_000a: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_000f: endfinally } // end handler IL_0010: ret ``` C# Source ``` private void ThrowException() { throw new Exception(); } private void _DoubleFinallyWithThrowingNewExceptionNotInline() { try { try { } finally { ThrowException(); } } finally { ThrowException(); } } ``` Why after first unreachable finally block infinite loop is generated? Why EndFinally OpCode is not generated? @Edit 1 Added some msil at Release mode. @Edit 2 Added example with non empty try exception The metadata .maxStack variable setted to 1, and existing .local variables are a bit confusing - there is no code connected with this variables. Debug: ``` .maxstack 1 .locals init ( [0] object someVar, [1] valuetype [mscorlib]System.DateTime ) IL_0000: nop .try { IL_0001: nop .try { IL_0002: nop IL_0003: ldarg.0 IL_0004: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_0009: nop IL_000a: nop IL_000b: leave.s IL_0014 } // end .try finally { IL_000d: nop IL_000e: newobj instance void [mscorlib]System.Exception::.ctor() IL_0013: throw } // end handler // loop start (head: IL_0014) IL_0014: br.s IL_0014 // end loop } // end .try finally { IL_0016: nop IL_0017: newobj instance void [mscorlib]System.Exception::.ctor() IL_001c: throw } // end handler ``` The previous object[0] has been skipped, but DateTime is still there. Release: ``` .maxstack 1 .locals init ( [0] valuetype [mscorlib]System.DateTime ) .try { .try { IL_0000: ldarg.0 IL_0001: call instance void System.Reflection.Emit.FactoryTests::ThrowException() IL_0006: leave.s IL_000e } // end .try finally { IL_0008: newobj instance void [mscorlib]System.Exception::.ctor() IL_000d: throw } // end handler // loop start (head: IL_000e) IL_000e: br.s IL_000e // end loop } // end .try finally { IL_0010: newobj instance void [mscorlib]System.Exception::.ctor() IL_0015: throw } // end handler` ``` C#: ``` private void _ExceptionLeaveReplacementAtFinallyAfterFinallyNonEmpty() { try { try { ThrowException(); } finally { throw new Exception(); } object someVar = DateTime.Now.GetHashCode(); } finally { throw new Exception(); } } ``` Or (Msil is identical): ``` private void _ExceptionLeaveReplacementAtFinallyAfterFinallyNonEmpty() { try { try { ThrowException(); } finally { throw new Exception(); } } finally { throw new Exception(); } object someVar = DateTime.Now.GetHashCode(); ```