Serious bugs with canceled / null conversions from int allowing conversion from decimal

I think this question will bring me instant fame here on Stack Overflow.

Suppose you have the following type:

// represents a decimal number with at most two decimal places after the period struct NumberFixedPoint2 { decimal number; // an integer has no fractional part; can convert to this type public static implicit operator NumberFixedPoint2(int integer) { return new NumberFixedPoint2 { number = integer }; } // this type is a decimal number; can convert to System.Decimal public static implicit operator decimal(NumberFixedPoint2 nfp2) { return nfp2.number; } /* will add more nice members later */ } 

It was written so that only safe conversions are allowed that do not lose accuracy. However, when I try this code:

  static void Main() { decimal bad = 2.718281828m; NumberFixedPoint2 badNfp2 = (NumberFixedPoint2)bad; Console.WriteLine(badNfp2); } 

I am surprised that this compiles and, when it starts, writes out 2 . It is important here to convert the int value (value 2 ) to NumberFixedPoint2 . (Overloading WriteLine , which takes the value a System.Decimal , is preferred if someone is wondering.)

Why is conversion from decimal to NumberFixedPoint2 allowed on Earth? (By the way, in the above code, if NumberFixedPoint2 changes from structure to class, nothing changes.)

Did you know that the C # language specification says that implicit conversion from int to user type implies the existence of a “direct” explicit conversion from decimal to this user type?

It gets a lot worse. Try using this code instead:

  static void Main() { decimal? moreBad = 7.3890560989m; NumberFixedPoint2? moreBadNfp2 = (NumberFixedPoint2?)moreBad; Console.WriteLine(moreBadNfp2.Value); } 

As you can see, we have (raised) Nullable<> conversions. But oh yes, it compiles.

When compiling on the x86 platform , this code produces an unpredictable numerical value. Which one changes from time to time. For example, I once received 2289956 Now this is a serious mistake!

When compiling for the x64 platform, the above code crashes the application with a System.InvalidProgramException with the message Common Language Runtime found an invalid program. . According to the documentation, the InvalidProgramException class:

This usually indicates an error in the compiler that generated the program.

Does anyone (like Eric Lippert or someone who worked with canceled conversions in the C # compiler) know the cause of these errors? How, what is a sufficient condition that we do not encounter them in our code? Since the type NumberFixedPoint2 is actually what we have in real code (we manage other people's money, etc.).

+59
c # nullable implicit-conversion compiler-bug
Aug 20 '13 at 18:55
source share
2 answers

Your second part (using nullable types) seems to be very similar to this known bug in the current compiler. From the answer to Connect question:

While we have no plans to solve this problem in the next version of Visual Studio, we plan to investigate the fix in Roslyn

Thus, this error will hopefully be fixed in a future version of Visual Studio and compilers.

+24
Aug 20 '13 at 19:07
source share

I just answer the first part of the question to get started. (I believe that the second part should be a separate issue, most likely it will be a mistake.)

There is only an explicit conversion from decimal to int , but that conversion is implicitly called in your code. The conversion occurs in this IL:

 IL_0010: stloc.0 IL_0011: ldloc.0 IL_0012: call int32 [mscorlib]System.Decimal::op_Explicit(valuetype [mscorlib]System.Decimal) IL_0017: call valuetype NumberFixedPoint2 NumberFixedPoint2::op_Implicit(int32) 

I believe that this is the correct behavior according to the specification, although this is surprising 1 . Let's go our way through section 6.4.5 of the C # 4 specification (Custom Explicit Conversions). I am not going to copy the entire text, because it would be tiring - it is precisely that in our case there are corresponding results. Similarly, I will not use indexes, as they do not work well with the code font here :)

  • Define types S0 and T0 : S0 is decimal , and T0 is NumberFixedPoint2 .
  • Find the set of D types from which the conversion operators used will be considered: just { decimal, NumberFixedPoint2 }
  • Find the set of applicable custom and canceled U conversion operators. decimal spans int (section 6.4.3) since there is a standard implicit conversion from int to decimal . Thus, the explicit conversion operator is in U and is indeed the only member of U
  • Find the most specific type of source, Sx , operators from U
    • The operator is not converted from S ( decimal ), so the first bullet is missing
    • The statement is not converted from a type that includes S ( decimal spans int , not vice versa), so the second bullet is missing
    • It just comes out of the third bullet that talks about the “most comprehensive type” - well, we only have one type, so good: Sx is int .
  • Find the most specific type of target, Tx , operators in U
    • The operator accesses directly NumberFixedPoint2 , therefore Tx NumberFixedPoint2 .
  • Find the most specific conversion operator:
    • U contains exactly one operator that actually converts from Sx to Tx , so the most specific operator
  • Finally, apply the conversion:
    • If S not Sx , then the standard explicit conversion from S to Sx is performed. (So ​​that decimal to int .)
    • The most specific custom conversion operator (your operator) is called
    • T Tx , so there is no need for conversion in the third brand

The line in bold is the bit that confirms that a standard explicit conversion is indeed possible when only an explicit conversion from another type is actually specified.




1 Well, it seemed surprising to me, at least. I do not know about this before.

+44
Aug 20 '13 at 19:15
source share



All Articles