Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler generates infinite loop after finally block when

Tags:

c#

roslyn

cil

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();
like image 531
Pixello Avatar asked Oct 21 '16 17:10

Pixello


People also ask

What causes an infinite loop in Java?

An infinite loop occurs when a condition always evaluates to true. Usually, this is an error. For example, you might have a loop that decrements until it reaches 0.

What causes an infinite loop error in a while statement?

Infinite loops This happens when the true/false expression never returns a false value. While there can be some cases where you might want this to happen, most often an infinite loop is the result of a bug in your program's logic.

Does Java compile infinite loop?

do while true java. We can create an infinite loop by passing boolean expression as true in the do while loop. Here is a simple do while java infinite loop example.

Does finally block run after return C#?

Yes, the finally block will be executed even after a return statement in a method. The finally block will always execute even an exception occurred or not in Java. If we call the System.


1 Answers

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".

like image 160
VSadov Avatar answered Oct 09 '22 16:10

VSadov