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.)
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.
When this gets compiled and we attempt to decompile it into C# with Reflector we get this.
Now wait just a second! Reflector has this all screwed up. This is what the IL actually looks like.
Notice that there is, in fact, no call to
PrepareConstrainedRegionsdespite what Reflector says. So where is it lurking? Well, it is right there in the auto generatedIEnumerator‘sMoveNextmethod. This time Reflector gets it right.And where did that call to
StackOverflowmysteriously move to? Right inside them_Finally2()method.So lets examine this a little more closely. We now have our
PrepareConstainedRegionscall inside atryblock instead of outside where it should be. And ourStackOverflowcall has moved from afinallyblock to atryblock.According to the documentation
PrepareConstrainedRegionsmust immediatly precede thetryblock. 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
tryblocks are not constrained. Onlycatch,finally, andfaultblocks are. And guess what? ThatStackOverflowcall got moved from afinallyblock to atryblock!