编译器在finally block后生成无限循环

本文关键字:无限循环 后生 block finally 编译器 | 更新日期: 2023-09-27 18:14:09

我使用的是针对。net 4.6.2的标准VS2015编译器。

编译器在finally块失败后发出无限循环。

一些例子:调试:

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

发布:

  .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

c#源代码

    private void _Simple()
    {
        try
        {
        }
        finally
        {
            for (;;) { }
        }
    }

如你所见,IL_000c是一个无限循环(由编译器生成)

好了,现在我给你们看一下扩展的大小写

调试:

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

发布:

.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

后嵌套finally再次生成无限循环,但后一秒finally不生成。(IL_000d)

源c#

    private void _DoubleFinallyWithThrowingNewException()
    {
        try
        {
            try
            {
            }
            finally
            {
                throw new Exception();
            }
        }
        finally
        {
            throw new Exception();
        }
    }

再一次,现在在finally块上调用的方法抛出了一个非显式异常。

调试:

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

发布:

    .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#源

    private void ThrowException()
    {
        throw new Exception();
    }
    private void _DoubleFinallyWithThrowingNewExceptionNotInline()
    {
        try
        {
            try
            {
            }
            finally
            {
                ThrowException();
            }
        }
        finally
        {
            ThrowException();
        }
    }

为什么在第一个不可达最后块无限循环生成?

为什么没有生成EndFinally OpCode ?

@Edit 1

在发布模式下增加了一些消息。

@Edit 2

添加了非空try异常的示例

元数据。maxstack变量设置为1,现有的。local变量有点混乱-没有与这些变量连接的代码。

调试:

.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

上一个对象[0]已被跳过,但DateTime仍然存在。发布:

.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();
        }
    }

或者(Msil相同):

    private void _ExceptionLeaveReplacementAtFinallyAfterFinallyNonEmpty()
    {
        try
        {
            try
            {
                ThrowException();
            }
            finally
            {
                throw new Exception();
            }
        }
        finally
        {
            throw new Exception();
        }
        object someVar = DateTime.Now.GetHashCode();

编译器在finally block后生成无限循环

这是设计。只要你不能到达那个无限循环:-)
谢谢你报告这个问题!!

===加长版:

当"finally"不终止(包含throw或无限循环)时,try语句之后的代码从语言的角度来看是不可访问的。因为它是不可访问的,所以允许它没有任何代码,即使,例如,该方法需要返回一个值。
事实上,由于通常在正常代码中保留的各种不变量在不可访问代码中不强制执行,编译器会防御性地删除不可访问的代码,即使它存在。这不仅仅是一种优化,它通常是正确性所必需的。与其阻止/检测/修复不可达代码中的违规,不如直接删除它。

现在,IL规范要求"leave"操作码指向有效的目标指令。特别是,它不关心分支是否被非终止的finally阻塞。但是我们没有任何有效的代码可以指向,所以我们需要注入一段"着陆"代码。它一定很小。我们也知道它永远无法到达,但是它也不能危及已经建立的方法的静态正确性。

一个无限循环就是这样一个最小的代码。
顺便说一句,另一种可能是"throw null",但历史上使用的是无限循环。

不,NOP不能工作,因为它会使下一个指令验证器可访问,这可能导致违反其他IL规则,如"不要通过方法的末尾,必须使用ret"。

好的,所以我挖掘了Roslyn源并找到了发生这种情况的确切位置:

在ILBuilder.cs的706行有一个私有方法叫做RewriteSpecialBlocks。它看起来像这样:

/// <summary>
/// Rewrite any block marked as BlockedByFinally as an "infinite loop".
/// </summary>
/// <remarks>
/// Matches the code generated by the native compiler in
/// ILGENREC::AdjustBlockedLeaveTargets.
/// </remarks>
private void RewriteSpecialBlocks()
{
    var current = leaderBlock;
    while (current != null)
    {
        // The only blocks that should be marked as BlockedByFinally
        // are the special blocks inserted at the end of exception handlers.
        Debug.Assert(current.Reachability != Reachability.BlockedByFinally ||
            IsSpecialEndHandlerBlock(current));
         if (IsSpecialEndHandlerBlock(current))
        {
            if (current.Reachability == Reachability.BlockedByFinally)
            {
                // BranchLabel points to the same block, so the BranchCode
                // is changed from Nop to Br_s.
                current.SetBranchCode(ILOpCode.Br_s);
            }
            else
            {
                // special block becomes a true nop
                current.SetBranch(null, ILOpCode.Nop);
            }
        }
        current = current.NextBlock;
    }
    // Now that the branch code has changed, the block is no longer special.
    Debug.Assert(AllBlocks(block => !IsSpecialEndHandlerBlock(block)));
}

这个方法是从这里调用的,注释表明这是不可达代码移除的全部部分。它仍然不能完全回答为什么它生成一个无限循环而不是一个nop

感谢这里的详细信息。从表面上看,这确实像是编译器中的一个bug。我已经提交了以下问题来跟踪这个问题。

https://github.com/dotnet/roslyn/issues/15297