【问题标题】:Compiler generates infinite loop after finally block when编译器在 finally 阻塞后生成无限循环
【发布时间】:2016-10-21 17:54:05
【问题描述】:

我正在使用针对 .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();
        }
    }

为什么在 first unreachable finally 块之后会生成无限循环?

为什么没有生成 EndFinally OpCode?​​p>

@编辑 1

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

@编辑 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();

【问题讨论】:

  • 在 RELEASE 模式下构建时的输出是什么?
  • 我不确定为什么要发出分支操作码,但 finally 块不需要有 endfinally 操作码。通过 throw 操作码离开 finally 块也是完全有效的(两个操作码之一必须存在;对于给定的 finally 块也可能存在多个操作码)。
  • 只有当 C# try 块完全为空时才会发生?
  • @JeppeStigNielsen 在 LINQPad 中测试:不,编译器仍然会生成无限循环“守卫”,即使 try 块中有内容。
  • 我推测 try 结束时的 leave.s 指令需要一些目标。需要“无用”的无限循环作为目标。

标签: c# roslyn cil


【解决方案1】:

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

=== 更长的版本:

当“finally”没有终止(包含抛出或无限循环)时,try 语句之后的代码在语言预期中变得不可访问。因为它是不可访问的,所以它允许没有任何代码,即使,例如,该方法需要返回一个值。
事实上,由于通常存在于普通代码中的各种不变量不会在不可访问代码中强制执行,因此编译器会防御性地删除即使存在不可访问的代码。这不仅仅是一种优化,它通常是正确性所必需的。与其防止/检测/修复无法访问的代码中的违规行为,不如将其删除更干净。

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

无限循环是这样的一小段代码。
顺便说一句,另一种可能性可能是“抛出 null”,但历史上使用的是无限循环。

不,NOP 不起作用,因为它会使下一条指令验证器可访问,这可能会导致违反其他 IL 规则,例如“不要从方法的末尾删除,必须使用 ret”。

【讨论】:

    【解决方案2】:

    好的,所以我挖掘了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 调用此方法,并且 cmets 表明这是无法访问代码删除的所有部分。它仍然没有完全回答为什么它会生成无限循环而不是nop

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2018-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-24
        • 1970-01-01
        • 2014-08-29
        • 1970-01-01
        • 2019-12-09
        相关资源
        最近更新 更多