Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the compiler evaluate remainder MinValue % -1 different than runtime?

Tags:

I think this looks like a bug in the C# compiler.

Consider this code (inside a method):

const long dividend = long.MinValue; const long divisor = -1L; Console.WriteLine(dividend % divisor); 

It compiles with no errors (or warnings). Seems like a bug. When run, prints 0 on console.

Then without the const, the code:

long dividend = long.MinValue; long divisor = -1L; Console.WriteLine(dividend % divisor); 

When this is run, it correctly results in an OverflowException being thrown.

The C# Language Specification mentions this case specifically and says a System.OverflowException shall be thrown. It does not depend on the context checked or unchecked it seems (also the bug with the compile-time constant operands to the remainder operator is the same with checked and unchecked).

Same bug happens with int (System.Int32), not just long (System.Int64).

For comparison, the compiler handles dividend / divisor with const operands much better than dividend % divisor.

My questions:

Am I right this is a bug? If yes, is it a well-known bug that they do not wish to fix (because of backwards compatibility, even if it is rather silly to use % -1 with a compile-time constant -1)? Or should we report it so that they can fix it in upcoming versions of the C# compiler?

like image 993
Jeppe Stig Nielsen Avatar asked Aug 18 '13 12:08

Jeppe Stig Nielsen


2 Answers

This corner-case is very specifically addressed in the compiler. Most relevant comments and code in the Roslyn source:

// Although remainder and division always overflow at runtime with arguments int.MinValue/long.MinValue and -1      // (regardless of checked context) the constant folding behavior is different.      // Remainder never overflows at compile time while division does.     newValue = FoldNeverOverflowBinaryOperators(kind, valueLeft, valueRight); 

And:

// MinValue % -1 always overflows at runtime but never at compile time     case BinaryOperatorKind.IntRemainder:     return (valueRight.Int32Value != -1) ? valueLeft.Int32Value % valueRight.Int32Value : 0; case BinaryOperatorKind.LongRemainder:     return (valueRight.Int64Value != -1) ? valueLeft.Int64Value % valueRight.Int64Value : 0; 

Also the behavior of the legacy C++ version of compiler, going all the way back to version 1. From the SSCLI v1.0 distribution, clr/src/csharp/sccomp/fncbind.cpp source file:

case EK_MOD:     // if we don't check this, then 0x80000000 % -1 will cause an exception...     if (d2 == -1) {         result = 0;     } else {         result = d1 % d2;     }     break; 

So conclusion to draw that this was not overlooked or forgotten about, at least by the programmers that worked on the compiler, it could perhaps be qualified as insufficiently precise language in the C# language specification. More about the runtime trouble caused by this killer poke in this post.

like image 146
Hans Passant Avatar answered Oct 03 '22 08:10

Hans Passant


I think it's not a bug; it's rather how C# compiler computes % (It's a guess). It seems that C# compiler first computes % for positive numbers, then applies the sign. Having Abs(long.MinValue + 1) == Abs(long.MaxValue) if we write:

static long dividend = long.MinValue + 1; static long divisor = -1L; Console.WriteLine(dividend % divisor); 

Now we will see 0 as the answer which is correct because now Abs(dividend) == Abs(long.MaxValue) which is in range.

Why it works when we declare it as a const value then? (Again a guess) It seems that C# compiler actually computes the expression at compile time and does not considers the type of the constant and act on it as a BigInteger or something (bug?). Because if we declare a function like:

static long Compute(long l1, long l2) {     return l1 % l2; } 

And call Console.WriteLine(Compute(dividend, divisor)); we will get the same exception. And again, if we declare the constant like this:

const long dividend = long.MinValue + 1; 

We would not get the exception.

like image 33
Kaveh Shahbazian Avatar answered Oct 03 '22 07:10

Kaveh Shahbazian