Apparently, Constrained Execution Region guarantees do not apply to iterators (probably because of how they are implemented and all), but is this a bug or by design? [See the example below.]
i.e. What are the rules on CERs being used with iterators?
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]; }
}
(Code mostly stolen from here.)
The do-while statement lets you repeat a statement or compound statement until a specified expression becomes false.
The C do while statement creates a structured loop that executes as long as a specified condition is true at the end of each pass through the loop.
Well, I do not know if this a bug or just a really weird edge case in which CERs were not designed to handle.
So here is the pertinent code.
private static IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
try { cerWorked = false; yield return 5; }
finally { StackOverflow(); }
}
When this gets compiled and we attempt to decompile it into C# with Reflector we get this.
private static IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
cerWorked = false;
yield return 5;
}
Now wait just a second! Reflector has this all screwed up. This is what the IL actually looks like.
.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
}
Notice that there is, in fact, no call to PrepareConstrainedRegions
despite what Reflector says. So where is it lurking? Well, it is right there in the auto generated IEnumerator
's MoveNext
method. This time Reflector gets it right.
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();
}
}
And where did that call to StackOverflow
mysteriously move to? Right inside the m_Finally2()
method.
private void <>m__Finally2()
{
this.<>1__state = -1;
Program.StackOverflow();
}
So lets examine this a little more closely. We now have our PrepareConstainedRegions
call inside a try
block instead of outside where it should be. And our StackOverflow
call has moved from a finally
block to a try
block.
According to the documentation PrepareConstrainedRegions
must immediatly precede the try
block. So the assumption is that it is ineffective if placed anywhere else.
But, even if the C# compiler got that part right things would be still be screwed up because try
blocks are not constrained. Only catch
, finally
, and fault
blocks are. And guess what? That StackOverflow
call got moved from a finally
block to a try
block!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With