using System; using System.Reflection; using System.Reflection.Emit; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Tests { /// /// These tests track known bugs in the CLR. The tests should fail when the tests /// no longer reproduce. /// [TestClass] public class ClrBugTests { /// /// Dynamic code gen computes the max stack size incorrectly, easily overflowing /// 16 bits. This can cause an . /// [TestMethod] public void MaxStackDepthOverflow() { Run((1 << 14) - 1); Assert.ThrowsException(() => Run((1 << 14) - 0)); // Throws! Run((1 << 14) + 1); void Run(int num) { Console.WriteLine("Start: {0}", num); var fn = GetCode(num); var val = fn(0); Console.WriteLine(" Val: {0}", val); } } /// /// The parameter is the number of basic blocks. Each has a max stack /// depth of four. There is one final basic block with max stack of one. The ILGenerator /// erroneously adds these, so the final value can overflow 2^16. When that result mod 2^16 /// is less than required, the CLR throws an . /// private static Func GetCode(int num) { var module = typeof(ClrBugTests).Module; var sts = new[] { typeof(object), typeof(long) }; var gen = new DynamicMethod("code", typeof(long), sts, module); var ilg = gen.GetILGenerator(); var loc = ilg.DeclareLocal(typeof(long)); ilg.Emit(OpCodes.Ldarg_1); ilg.Emit(OpCodes.Stloc, loc); for (int i = 0; i < num; i++) { ilg.Emit(OpCodes.Ldloc, loc); ilg.Emit(OpCodes.Ldc_I4_1); ilg.Emit(OpCodes.Ldc_I4_1); ilg.Emit(OpCodes.Ldc_I4_2); ilg.Emit(OpCodes.Add); ilg.Emit(OpCodes.Add); ilg.Emit(OpCodes.Add); ilg.Emit(OpCodes.Stloc, loc); // Unconditional jump to next block. var labNext = ilg.DefineLabel(); ilg.Emit(OpCodes.Br, labNext); ilg.MarkLabel(labNext); } ilg.Emit(OpCodes.Ldloc, loc); ilg.Emit(OpCodes.Ret); var fin = typeof(ILGenerator).GetField("m_maxStackSize", BindingFlags.NonPublic | BindingFlags.Instance); Assert.IsNotNull(fin); Assert.AreEqual(typeof(int), fin.FieldType); var stack = (int)fin.GetValue(ilg); Console.WriteLine(" Stk: {0}, {1}", stack, stack & 0xFFFF); var res = gen.CreateDelegate(typeof(Func), null); return (Func)res; } } }