编译器在最终阻塞后生成无限循环

时间:2016-10-21 17:54:06

标签: c# roslyn cil

我正在使用针对.Net 4.6.2的标准VS2015编译器。

Compilator在最终阻止失败后发出无限循环。

一些例子:

调试:

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

嵌套后终于再次生成无限循环,但是第二次终于没有。 (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();
        }
    }

为什么在第一次无法访问finally块后会产生无限循环?

为什么没有生成EndFinally OpCode?

@Edit 1

在发布模式下添加了一些msil。

@Edit 2

添加了非空尝试异常的示例

将元数据.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();

3 个答案:

答案 0 :(得分:8)

这是设计的。只要你无法达到无限循环:-)
感谢您报告此问题!!!

===较长版本:

"最后"不终止(包含throw或无限循环),try语句之后的代码变得无法访问语言。由于无法访问,因此无论如何都不允许使用任何代码,即使例如方法需要返回值。 实际上,因为通常在普通代码中保存的各种不变量不会在无法访问的代码中强制执行,所以编译器会防御性地删除即使存在也无法访问的代码。这不仅仅是一种优化,通常还需要正确性。

可以更清楚地删除无法访问的代码,而不是阻止/检测/修复无法访问的代码中的违规行为。

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

无限循环是这样的最小代码 BTW,另一种可能性是"抛出null",但历史上使用无限循环。

不,NOP不起作用,因为它会使下一个指令验证程序可达,并且可能导致违反其他IL规则,例如"不要掉到方法的末尾,必须使用ret&#34 ;

答案 1 :(得分:7)

好的,所以我挖掘了Roslyn Source并找到了确切的位置:

第706行的ILBuilder.cs中有一个名为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)));
}

此方法从here调用,注释表明这是无法访问代码的所有部分。它仍然不能完全回答为什么它会产生无限循环而不是nop

答案 2 :(得分:5)

感谢此处的详细信息。从表面上看,这似乎是编译器中的一个错误。我已经提交了以下问题来跟踪此事。

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