Пробиваются ли CER C # try-finally в итераторах? - PullRequest
18 голосов
/ 28 июля 2011

По-видимому, гарантии области ограниченного выполнения не распространяются на итераторы (возможно, из-за того, как они реализованы и все такое), но это ошибка или дизайн? [См. Пример ниже.]

т.е. Каковы правила использования ССВ с итераторами?

using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;

class Program
{
    static bool cerWorked;
    static void Main(string[] args)
    {
        try
        {
            cerWorked = true;
            foreach (var v in Iterate()) { }
        }
        catch { System.Console.WriteLine(cerWorked); }
        System.Console.ReadKey();
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    unsafe static void StackOverflow()
    {
        Big big;
        big.Bytes[int.MaxValue - 1] = 1;
    }

    static System.Collections.Generic.IEnumerable<int> Iterate()
    {
        RuntimeHelpers.PrepareConstrainedRegions();
        try { cerWorked = false; yield return 5; }
        finally { StackOverflow(); }
    }

    unsafe struct Big { public fixed byte Bytes[int.MaxValue]; }
}

(код, в основном украденный у здесь .)

1 Ответ

15 голосов
/ 28 июля 2011

Ну, я не знаю, является ли это ошибкой или просто действительно странным крайним случаем, в котором CER не были предназначены для обработки.

Так вот соответствующий код.

private static IEnumerable<int> Iterate()
{
    RuntimeHelpers.PrepareConstrainedRegions();
    try { cerWorked = false; yield return 5; }
    finally { StackOverflow(); }
}

Когда это скомпилируется и мы пытаемся декомпилировать его в C # с помощью Reflector, мы получаем это.

private static IEnumerable<int> Iterate()
{
    RuntimeHelpers.PrepareConstrainedRegions();
    cerWorked = false;
    yield return 5;
}

Теперь подожди секунду! Отражатель все это облажался. Вот как на самом деле выглядит ИЛ.

.method private hidebysig static class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> Iterate() cil managed
{
    .maxstack 2
    .locals init (
        [0] class Sandbox.Program/<Iterate>d__1 d__,
        [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> enumerable)
    L_0000: ldc.i4.s -2
    L_0002: newobj instance void Sandbox.Program/<Iterate>d__1::.ctor(int32)
    L_0007: stloc.0 
    L_0008: ldloc.0 
    L_0009: stloc.1 
    L_000a: br.s L_000c
    L_000c: ldloc.1 
    L_000d: ret 
}

Обратите внимание, что на самом деле нет вызова PrepareConstrainedRegions, несмотря на то, что говорит Reflector. Так где же это скрывается? Ну, это прямо в автоматически сгенерированном методе IEnumerator * MoveNext. На этот раз Reflector делает все правильно.

private bool MoveNext()
{
    try
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                RuntimeHelpers.PrepareConstrainedRegions();
                this.<>1__state = 1;
                Program.cerWorked = false;
                this.<>2__current = 5;
                this.<>1__state = 2;
                return true;

            case 2:
                this.<>1__state = 1;
                this.<>m__Finally2();
                break;
        }
        return false;
    }
    fault
    {
        this.System.IDisposable.Dispose();
    }
}

А куда таинственным образом переместился этот призыв к StackOverflow? Прямо внутри m_Finally2() метода.

private void <>m__Finally2()
{
    this.<>1__state = -1;
    Program.StackOverflow();
}

Итак, давайте рассмотрим это немного более внимательно. Теперь у нас есть PrepareConstainedRegions вызов внутри блока try вместо того, где он должен быть. И наш StackOverflow вызов переместился из блока finally в блок try.

В соответствии с документацией PrepareConstrainedRegions должен непосредственно предшествовать блоку try. Таким образом, предполагается, что он неэффективен, если размещен где-либо еще.

Но, даже если компилятор C # правильно понял эту часть, все равно все будет испорчено, потому что блоки try не ограничены. Только блоки catch, finally и fault. И угадай что? Этот StackOverflow вызов был перемещен из блока finally в блок try!

...