2011-07-28 7 views
17

Aparentemente, las garantías de la Región de Ejecución Restringida no se aplican a los iteradores (probablemente debido a cómo se implementan y todo), pero ¿es esto un error o diseño? [Vea el ejemplo a continuación.]Do C# try-finally ¿Las CERs se rompen en los iteradores?

es decir, ¿cuáles son las reglas sobre las CER que se utilizan con los iteradores?

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]; } 
} 

(Código robados en su mayoría de here.)

+2

Por lo que vale, parece ser la primera persona en notar esto ... al menos hasta donde pude ver en Google para otras referencias de la misma. –

+0

Encontré este https://vmccontroller.svn.codeplex.com/svn/VmcController/VmcServices/DetectOpenFiles.cs fragmento de código en el que el confiado autor no va a obtener el CER que cree que está obteniendo. –

+0

@Brian: Lol, agradable. Creo que es algo que la mayoría de la gente no usa muy a menudo, y los que probablemente ya lo saben intuitivamente, sin haber pensado en ello. Solo pienso, sin embargo. – Mehrdad

Respuesta

14

Bueno, no sé si esto un error o simplemente un caso raro borde realmente RCE en el que no fueron diseñados para manejar.

Así que aquí está el código pertinente.

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

Cuando esto se compila e intentamos descompilarlo en C# con Reflector obtenemos esto.

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

¡Ahora espere un segundo! El reflector ha estropeado todo esto. Así es como se ve realmente el IL.

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

anuncio de que no es, de hecho, no hay ninguna llamada a PrepareConstrainedRegions pesar de lo que dice el reflector. Entonces, ¿dónde está al acecho? Bueno, está justo allí en el IEnumeratorIEnumerator método auto generado MoveNext. Esta vez, Reflector lo hace bien.

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

Y de dónde vino esa llamada a StackOverflow mueven misteriosamente? Justo dentro del método m_Finally2().

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

Así que vamos a examinar esto un poco más de cerca. Ahora tenemos nuestra llamada PrepareConstainedRegions dentro de un bloque try en lugar de afuera donde debería estar. Y nuestra llamada StackOverflow se ha movido de un bloque finally a un bloque try.

De acuerdo con documentationPrepareConstrainedRegions debe preceder inmediatamente al bloque try. Entonces, la suposición es que es ineficaz si se coloca en otro lugar.

Pero, incluso si el compilador de C# obtuviera esa parte correcta, las cosas seguirían estando dañadas porque los bloques try no están restringidos. Solo están los bloques catch, finally y fault. ¿Y adivina qué? ¡Esa llamada StackOverflow se movió de un bloque finally a un bloque try!

+0

+1, buena respuesta, pero ¿qué es el bloque 'fault'? ** Editar: ** no importa, es algo relacionado con [rendimiento] (http://startbigthinksmall.wordpress.com/2008/06/09/behind-the-scenes-of-the-c-yield-keyword/) –

+1

@Jalal: No, * no * está relacionado con el rendimiento. Es simplemente 'atrapar {... arrojar; } ', sin la declaración adicional' throw'. (Dado que son prácticamente lo mismo, no es una característica de C#). – Mehrdad

+1

@Jalal Un bloque de falla es como un bloque finally, pero solo se ejecuta cuando el control se va debido a una excepción. [Aquí hay un poco más sobre eso.] (Http://www.simple-talk.com/community/blogs/simonc/archive/2011/02/09/99250.aspx) El compilador _ lo usa_ en su implementación de una máquina de estado enumerable, pero no es específica de la palabra clave yield. –

Cuestiones relacionadas