We are using a struct that encapsulates numeric values and I found out when the nullable version of this struct is used in an expression, a FatalExecutionEngineError
happens:
Additional information: The runtime has encountered a fatal error. The
address of the error was at 0x729c1e04, on thread 0x52d8. The error
code is 0xc0000005. This error may be a bug in the CLR or in the
unsafe or non-verifiable portions of user code. Common sources of this
bug include user marshaling errors for COM-interop or PInvoke, which
may corrupt the stack.
I am using Visual Studio Premium 2013 Update 3
Here is the source code (C#, target .NET Framework 4.5):
using System;
using System.Globalization;
namespace ConsoleApplication4
{
public struct Number
{
ValueType _val;
private Number(double val)
{
this._val = val;
}
public static implicit operator double(Number val)
{
return Convert.ToDouble(val._val, CultureInfo.InvariantCulture);
}
public static implicit operator Number(double val)
{
return new Number(val);
}
}
class Program
{
static void Main(string[] args)
{
Number? b = 1.2;
var c = b - 1.2;
Number b1 = 1.2;
var c1 = b1 - 1.2;
}
}
}
Note, adding this solves the issue, so it is not urgent, however I am very interested why this issue is actually happening.
public static implicit operator double(Number? val)
{
return Convert.ToDouble(val.GetValueOrDefault()._val, CultureInfo.InvariantCulture);
}
This looks like a compiler bug. The problem happens on the second line of Main
.
Number? b = 1.2;
var c = b - 1.2; // The problem lies here
Note the IL generated by VS2013, the issue is with IL_005C
and surrounded code, which is unnecessarily generated:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 116 (0x74)
.maxstack 2
.locals init (valuetype [mscorlib]System.Nullable`1<valuetype Test.Number> V_0,
valuetype [mscorlib]System.Nullable`1<float64> V_1,
valuetype [mscorlib]System.Nullable`1<valuetype Test.Number> V_2,
valuetype [mscorlib]System.Nullable`1<float64> V_3,
valuetype [mscorlib]System.Nullable`1<float64> V_4)
// Number? b = 1.2;
IL_0000: nop
IL_0001: ldloca.s V_0 //b
IL_0003: ldc.r8 1.2
IL_000c: call valuetype Test.Number Test.Number::op_Implicit(float64)
IL_0011: call instance void valuetype [mscorlib]System.Nullable`1<valuetype Test.Number>::.ctor(!0)
IL_0016: nop
IL_0017: ldloc.0
IL_0018: stloc.2 // b
// var c = b - 1.2;
IL_0019: ldloca.s V_2 // b
IL_001b: call instance bool valuetype [mscorlib]System.Nullable`1<valuetype Test.Number>::get_HasValue()
IL_0020: brtrue.s IL_002d
IL_0022: ldloca.s V_3
IL_0024: initobj valuetype [mscorlib]System.Nullable`1<float64>
IL_002a: ldloc.3
IL_002b: br.s IL_003e
IL_002d: ldloca.s V_2
IL_002f: call instance !0 valuetype [mscorlib]System.Nullable`1<valuetype Test.Number>::GetValueOrDefault()
IL_0034: call float64 Test.Number::op_Implicit(valuetype Test.Number)
// Um, what? First part of compiler bug is that it's needlessly creating a nullable float
IL_0039: newobj instance void valuetype [mscorlib]System.Nullable`1<float64>::.ctor(!0)
IL_003e: nop
IL_003f: stloc.3
IL_0040: ldloca.s V_3
IL_0042: call instance bool valuetype [mscorlib]System.Nullable`1<float64>::get_HasValue()
IL_0047: brtrue.s IL_0055
IL_0049: ldloca.s V_4
IL_004b: initobj valuetype [mscorlib]System.Nullable`1<float64>
IL_0051: ldloc.s V_4
IL_0053: br.s IL_0071
IL_0055: ldloca.s V_3
// Here's the real bug, though. It's passing float64 to a the op_Implicit that is expecting a Number struct
IL_0057: call instance !0 valuetype [mscorlib]System.Nullable`1<float64>::GetValueOrDefault()
IL_005c: call float64 Test.Number::op_Implicit(valuetype Test.Number)
IL_0061: conv.r8
IL_0062: ldc.r8 1.2
IL_006b: sub
IL_006c: newobj instance void valuetype [mscorlib]System.Nullable`1<float64>::.ctor(!0)
IL_0071: nop
IL_0072: stloc.1
IL_0073: ret
} // end of method Program::Main
Roslyn (also VS14 CTP, which uses it) produces different IL which doesn't have this issue. The code is almost identical, except that it omits the IL between IL_0039
and IL_0061
.