Nasty bug in the C# compiler, it truncates values on the stack

J

Jerry

I managed to narrow this down to a very simple expression. try this:

private void Bug()
{
bool b = false;
Test(3, (b || b) && b && !b);
}

private void Works()
{
bool b = true;
Test(3, (b || b) && b && !b);
}

private void Test(decimal v, bool b)
{
MessageBox.Show(v.ToString());
}

The value of the argument v in Test() is 0!

If you change the function Bug() and make bool b = true it works fine. I
checked the MSIL code and it appears that the compiler chocked on the
expression "(b || b) && b && !b" and silently truncated the previous
argument to bool (1 byte). Since the argument is a decimal it will show zero
because the truncation occurs on the decimal structure.

You can try this with any struct and any value as the first argument, the
compiler will always truncate the struct on the stack to 1 byte. If you try
with your own struct you will lose all the data after the first byte.

Strangely enough, if you remove the parenthesis "b || b && b && !b" is works
fine.

Plese don't focus on the expression, it's a simplification of a real
expression. The nastiest part is that there is no compiler error or warning,
it simply generates wrong MSIL code.

Is this a known bug? If not, how do I report this to Microsoft and to the
..NET team?

I tried in the past to report a bug with the shortcircuit operators specs
and compiler but didn't suceeded and the defect is still there.

I also tried with the latest service pack, the version shown in the .NET
Configuration module is version 1.1.4322.573.
 
W

Willy Denoyette [MVP]

Jerry said:
I managed to narrow this down to a very simple expression. try this:

private void Bug()
{
bool b = false;
Test(3, (b || b) && b && !b);
}

private void Works()
{
bool b = true;
Test(3, (b || b) && b && !b);
}

private void Test(decimal v, bool b)
{
MessageBox.Show(v.ToString());
}

The value of the argument v in Test() is 0!

If you change the function Bug() and make bool b = true it works fine. I
checked the MSIL code and it appears that the compiler chocked on the
expression "(b || b) && b && !b" and silently truncated the previous
argument to bool (1 byte). Since the argument is a decimal it will show
zero
because the truncation occurs on the decimal structure.

You can try this with any struct and any value as the first argument, the
compiler will always truncate the struct on the stack to 1 byte. If you
try
with your own struct you will lose all the data after the first byte.

Strangely enough, if you remove the parenthesis "b || b && b && !b" is
works
fine.

Plese don't focus on the expression, it's a simplification of a real
expression. The nastiest part is that there is no compiler error or
warning,
it simply generates wrong MSIL code.

Is this a known bug? If not, how do I report this to Microsoft and to the
.NET team?

I tried in the past to report a bug with the shortcircuit operators specs
and compiler but didn't suceeded and the defect is still there.

I also tried with the latest service pack, the version shown in the .NET
Configuration module is version 1.1.4322.573.

Yep, looks like a bug.

You can report bugs/issues to
http://lab.msdn.microsoft.com/productfeedback/default.aspx

Willy.
 
D

Daniel O'Connell [C# MVP]

Plese don't focus on the expression, it's a simplification of a real
expression. The nastiest part is that there is no compiler error or
warning,
it simply generates wrong MSIL code.

What did you find wrong with the MSIL? I can't see a specific problem with
it(Manual trace doesn't cause a truncation that I can see). Are you sure
this is the C# compiler and not the JIT?
 
G

Gianluca

No, you are right. I was looking at a bool cast that my code applies. I was
checking the MSIL from the original code and it's more complex that the
sample expression. I just checked the MSIL from the test code and there is
no visible difference (other than the assigment) between the b = false (bug)
and the b = true (no bug) calls. Looking at the MSIL below is it possible
that when L_0019 is reached the first argument on the stack has been
truncated?

How can it be the JIT if the same code works when b = true?

// Code Size: 31 byte(s)
.maxstack 4
.locals (
bool flag1)
L_0000: ldc.i4.0 <---- when this is ldc.i4.1 the code works OK.
L_0001: stloc.0
L_0002: ldarg.0
L_0003: ldc.i4.3
L_0004: newobj instance void [mscorlib]System.Decimal::.ctor(int32)
L_0009: ldloc.0
L_000a: brtrue.s L_000f
L_000c: ldloc.0
L_000d: brfalse.s L_0018
L_000f: ldloc.0
L_0010: brfalse.s L_0018
L_0012: ldloc.0
L_0013: ldc.i4.0
L_0014: ceq
L_0016: br.s L_0019
L_0018: ldc.i4.0
L_0019: call instance void
CSCompilerBug.Form1::Test([mscorlib]System.Decimal, bool)
L_001e: ret
 
D

Daniel O'Connell [C# MVP]

Gianluca said:
No, you are right. I was looking at a bool cast that my code applies. I
was
checking the MSIL from the original code and it's more complex that the
sample expression. I just checked the MSIL from the test code and there
is
no visible difference (other than the assigment) between the b = false
(bug)
and the b = true (no bug) calls. Looking at the MSIL below is it possible
that when L_0019 is reached the first argument on the stack has been
truncated?

How can it be the JIT if the same code works when b = true?

I would guess its an optimization bug where the JIT spits out code based on
a stack value it already knows(its possible to determine what code will
execute statically after all), but the bug could just be somewhere on the
path that is followed when the boolean is false.

Has anyone tested this on any of the 2.0 betas?
 
M

Mattias Sjögren

Has anyone tested this on any of the 2.0 betas?

I can't repro with beta 2.



Mattias
 
W

Willy Denoyette [MVP]

Daniel O'Connell said:
I would guess its an optimization bug where the JIT spits out code based
on a stack value it already knows(its possible to determine what code will
execute statically after all), but the bug could just be somewhere on the
path that is followed when the boolean is false.

Has anyone tested this on any of the 2.0 betas?

Works as expected in v2.0 Beta2.

Willy.
 
G

Gianluca

it sounds strange, it doesn't work even if the value of be is not knows at
compile time. also the same code works when b is true. I don't think the JIT
regenerates code in the fly. it would be interesting to dissassemble and
see.
 
F

Frans Bouma [C# MVP]

Daniel said:
I would guess its an optimization bug where the JIT spits out code based on
a stack value it already knows(its possible to determine what code will
execute statically after all), but the bug could just be somewhere on the
path that is followed when the boolean is

After reading this post, I think indeed it's the JIT and not the
compiler optimization (first thought the compiler was it)

I now also think it's jit optimization related where it knows that the
boolean expression can never be true, because b is false. It therefore
pops operators and operands from the stack (that's a guess) and it does
that wrong, so too less data is left on the stack.

making b is true fixes it, which means that the optimization trick '(b
|| b) &&.. can never be true' can't be used, and hte complete expression
has to be evaluated, avoiding the popping and therefore will result in
working code.

workaround: evaluate the expression into a variable, then pass that
variable.

FB

--
 
G

Gianluca

the optmizer cannot know that b will never be true, the same code doesn't
work if b is evaluated at runtime.
actually it's enough to remove the parenthesis and the code works!
 
M

Martin Knopp

I think it only can be the JIT - there are two reasons for this:

1. The generated IL seems to be correct to me.

2. Would it at all be possible to pop only part (remember, only 1 byte
remains according to Gianluca) of an argument from the stack using IL?

--
Greetings from Vienna,
Martin

fecher GmbH
good people - good software
 
F

Frans Bouma [C# MVP]

Gianluca said:
the optmizer cannot know that b will never be true, the same code doesn't
work if b is evaluated at runtime.

it knows b is false, it's set to false at the start. (b || b) &&...
will then evaluate to false, no matter what. If the code sets b to true,
no bug appears, which IMHO shows that the bug is likely in the shortcut
code to evaluate boolean expressions.
actually it's enough to remove the parenthesis and the code works!

Hmm... that's indeed strange.
you could argue that
b || b && exp
is different from
(b || b) && exp

as the JIT could see it as:
b || (b && exp)
vs.
(b || b) && exp

though that would trigger the question: why does it resolve (b || b)
but not b || b ?

FB

--
 
C

Chris Taylor

Well, I have been watching this thread to see what comes up. Tonight I took
a shot at it and after about 45min of playing around this is what I have
come up with. Unfortunately there is nothing conclusive other than it does
appear to be a JIT bug, but hey everyone guessed that already :). And my
evaluation of the code might be off since it was just a quick scan of what
is happening.

Below is the non-optimized JIT code generated for the expression. I have
included the IL instuctions that map to the JIT code and you will notice
that the ldloc instructions which load the variable b onto the evaluation
stack move data from the esi to the dsi. Now notice the ldloc at label
IL_000c in this case the JIT code loads the same value into the edi and esi
registers, this throws the evaluation stack out and causing the chaos.

For the other expressions the only difference is that this particular
section of the code is not executed and the problem is hidden, but still
exists. The bug does appear to be with the JIT, and it would appear that the
short circuit evaluation is in face hidding the problem rather than causing
it, I say this because of the short circuit evaluation the erroneous code is
not executed in cases where the variable is true. This is also true for the
case where the brackets are removed from the expression.

It would be very interesting to isolate the exact senarios where this
erroneous code is generated, but I have confirmed that it is when a
non-primitive value type (ie. Decimal, DateTime or User Defined value types)
exists on the evaluation stack prior to the expression that this code is
generated.

IL_0003: ldc.i4.3
0000001c mov esi,3
IL_0004: newobj instance void
[mscorlib]System.Decimal::.ctor(int32)
00000021 lea ecx,[ebp-18h]
00000024 mov edx,esi
00000026 call dword ptr ds:[79C41A78h]
IL_0009: ldloc.0
0000002c mov dword ptr [ebp-1Ch],ebx
0000002f mov dword ptr [ebp-20h],edi
00000032 lea edi,[ebp-30h]
00000035 lea esi,[ebp-18h]
00000038 movs dword ptr [edi],dword ptr [esi]
00000039 movs dword ptr [edi],dword ptr [esi]
0000003a movs dword ptr [edi],dword ptr [esi]
0000003b movs dword ptr [edi],dword ptr [esi]
IL_000a: brtrue.s IL_000f
0000003c cmp dword ptr [ebp-1Ch],0
00000040 jne 0000005B
//------------Here is here I believe the problem is in the JIT code
IL_000c: ldloc.0
00000042 mov dword ptr [ebp-64h],ebx
00000045 mov eax,dword ptr [ebp-20h]
00000048 mov dword ptr [ebp-20h],eax
0000004b lea edi,[ebp-30h] //<------- Prblem on this and next line,
edi/esi are loaded with same value so
0000004e lea esi,[ebp-30h] // evaluation stack is not adjusted
00000051 movs dword ptr [edi],dword ptr [esi]
00000052 movs dword ptr [edi],dword ptr [esi]
00000053 movs dword ptr [edi],dword ptr [esi]
00000054 movs dword ptr [edi],dword ptr [esi]
//---------------------------------------------------------------
IL_000d: brfalse.s IL_0018
00000055 cmp dword ptr [ebp-64h],0
00000059 je 000000CC
IL_000f: ldloc.0
0000005b mov dword ptr [ebp-34h],ebx
0000005e mov eax,dword ptr [ebp-20h]
00000061 mov dword ptr [ebp-38h],eax
00000064 lea edi,[ebp-48h]
00000067 lea esi,[ebp-30h]
0000006a movs dword ptr [edi],dword ptr [esi]
0000006b movs dword ptr [edi],dword ptr [esi]
0000006c movs dword ptr [edi],dword ptr [esi]
0000006d movs dword ptr [edi],dword ptr [esi]
IL_0010: brfalse.s IL_0018

--
Chris Taylor
http://dotnetjunkies.com/weblog/chris.taylor


Frans Bouma said:
Gianluca said:
the optmizer cannot know that b will never be true, the same code doesn't
work if b is evaluated at runtime.

it knows b is false, it's set to false at the start. (b || b) &&...
will then evaluate to false, no matter what. If the code sets b to true,
no bug appears, which IMHO shows that the bug is likely in the shortcut
code to evaluate boolean expressions.
actually it's enough to remove the parenthesis and the code works!

Hmm... that's indeed strange.
you could argue that
b || b && exp
is different from
(b || b) && exp

as the JIT could see it as:
b || (b && exp)
vs.
(b || b) && exp

though that would trigger the question: why does it resolve (b || b)
but not b || b ?

FB
Daniel O'Connell [C# MVP] wrote:



No, you are right. I was looking at a bool cast that my code applies. I
was
checking the MSIL from the original code and it's more complex that the
sample expression. I just checked the MSIL from the test code and there
is
no visible difference (other than the assigment) between the b = false
(bug)
and the b = true (no bug) calls. Looking at the MSIL below is it
possible

that when L_0019 is reached the first argument on the stack has been
truncated?

How can it be the JIT if the same code works when b = true?


I would guess its an optimization bug where the JIT spits out code
based

on
a stack value it already knows(its possible to determine what code will
execute statically after all), but the bug could just be somewhere on
the

path that is followed when the boolean is

After reading this post, I think indeed it's the JIT and not the
compiler optimization (first thought the compiler was it)

I now also think it's jit optimization related where it knows that the
boolean expression can never be true, because b is false. It therefore
pops operators and operands from the stack (that's a guess) and it does
that wrong, so too less data is left on the stack.

making b is true fixes it, which means that the optimization trick '(b
|| b) &&.. can never be true' can't be used, and hte complete expression
has to be evaluated, avoiding the popping and therefore will result in
working code.

workaround: evaluate the expression into a variable, then pass that
variable.

--
------------------------------------------------------------------------
Get LLBLGen Pro, productive O/R mapping for .NET: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
G

Gianluca

it's set at the start in the sample code. you can pass it as a parameter or
load it from a file or calculate it and it still does not work.

Frans Bouma said:
Gianluca said:
the optmizer cannot know that b will never be true, the same code doesn't
work if b is evaluated at runtime.

it knows b is false, it's set to false at the start. (b || b) &&...
will then evaluate to false, no matter what. If the code sets b to true,
no bug appears, which IMHO shows that the bug is likely in the shortcut
code to evaluate boolean expressions.
actually it's enough to remove the parenthesis and the code works!

Hmm... that's indeed strange.
you could argue that
b || b && exp
is different from
(b || b) && exp

as the JIT could see it as:
b || (b && exp)
vs.
(b || b) && exp

though that would trigger the question: why does it resolve (b || b)
but not b || b ?

FB
Daniel O'Connell [C# MVP] wrote:



No, you are right. I was looking at a bool cast that my code applies. I
was
checking the MSIL from the original code and it's more complex that the
sample expression. I just checked the MSIL from the test code and there
is
no visible difference (other than the assigment) between the b = false
(bug)
and the b = true (no bug) calls. Looking at the MSIL below is it
possible

that when L_0019 is reached the first argument on the stack has been
truncated?

How can it be the JIT if the same code works when b = true?


I would guess its an optimization bug where the JIT spits out code
based

on
a stack value it already knows(its possible to determine what code will
execute statically after all), but the bug could just be somewhere on
the

path that is followed when the boolean is

After reading this post, I think indeed it's the JIT and not the
compiler optimization (first thought the compiler was it)

I now also think it's jit optimization related where it knows that the
boolean expression can never be true, because b is false. It therefore
pops operators and operands from the stack (that's a guess) and it does
that wrong, so too less data is left on the stack.

making b is true fixes it, which means that the optimization trick '(b
|| b) &&.. can never be true' can't be used, and hte complete expression
has to be evaluated, avoiding the popping and therefore will result in
working code.

workaround: evaluate the expression into a variable, then pass that
variable.

--
------------------------------------------------------------------------
Get LLBLGen Pro, productive O/R mapping for .NET: http://www.llblgen.com
My .NET blog: http://weblogs.asp.net/fbouma
Microsoft MVP (C#)
------------------------------------------------------------------------
 
D

Dat Bui [MSFT]

Hi Jerry,

This issue was brought to my attention by a PM for C# that was reading
through this thread. I want to take this issue and formally bring this up
to our products team so we cna track it. Please email me directly with
your contact infomation and I can create a free support incident and we can
start debugging this issue. You can email me directly, remove the "online
.." in my email:

(e-mail address removed)

Once we have resolved this issue, I will make a final post to this thread
with the resolution.

Thanks,
Dat Bui
Microsoft Developer Support

This posting is provided "AS IS" with no warranties, and confers no rights.
© Microsoft Corporation. All rights reserved.

--------------------
 
D

Dat Bui [MSFT]

Looks like this bug has been reported and is fixed in Whidbey. You can
review the bug here:

http://lab.msdn.microsoft.com/productfeedback/viewfeedback.aspx?feedbackid=9
439dd52-e87c-476b-a8fe-b04835dabff9

Hope this helps,
Dat Bui
Microsoft Developer Support

This posting is provided "AS IS" with no warranties, and confers no rights.
© Microsoft Corporation. All rights reserved.

--------------------
X-Tomcat-ID: 538564087
References: <q68ee.10$fQ2.2@trnddc05>
MIME-Version: 1.0
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
From: (e-mail address removed) (Dat Bui [MSFT])
Organization: Microsoft
Date: Wed, 25 May 2005 20:53:04 GMT
Subject: RE: Nasty bug in the C# compiler, it truncates values on the stack
X-Tomcat-NG: microsoft.public.dotnet.languages.csharp
Message-ID: <[email protected]>
Newsgroups: microsoft.public.dotnet.languages.csharp
Lines: 87
Path: TK2MSFTNGXA01.phx.gbl
Xref: TK2MSFTNGXA01.phx.gbl microsoft.public.dotnet.languages.csharp:100699
NNTP-Posting-Host: tomcatimport2.phx.gbl 10.201.218.182

Hi Jerry,

This issue was brought to my attention by a PM for C# that was reading
through this thread. I want to take this issue and formally bring this up
to our products team so we cna track it. Please email me directly with
your contact infomation and I can create a free support incident and we can
start debugging this issue. You can email me directly, remove the "online
." in my email:

(e-mail address removed)

Once we have resolved this issue, I will make a final post to this thread
with the resolution.

Thanks,
Dat Bui
Microsoft Developer Support

This posting is provided "AS IS" with no warranties, and confers no rights.
© Microsoft Corporation. All rights reserved.

--------------------
Reply-To: "Jerry" <[email protected]>
From: "Jerry" <[email protected]>
Newsgroups: microsoft.public.dotnet.languages.csharp
Subject: Nasty bug in the C# compiler, it truncates values on the stack
Lines: 48
Organization: Goodman
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 6.00.2800.1106
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1106
Message-ID: <q68ee.10$fQ2.2@trnddc05>
Date: Wed, 04 May 2005 18:01:58 GMT
NNTP-Posting-Host: 70.21.88.43
X-Complaints-To: (e-mail address removed)
X-Trace: trnddc05 1115229718 70.21.88.43 (Wed, 04 May 2005 14:01:58 EDT)
NNTP-Posting-Date: Wed, 04 May 2005 14:01:58 EDT
Path: TK2MSFTNGXA01.phx.gbl!TK2MSFTNGP08.phx.gbl!newsfeed00.sul.t-online.de!t-onl
i
ne.de!border2.nntp.dca.giganews.com!nntp.giganews.com!cyclone1.gnilink.net!
s
pamkiller.gnilink.net!gnilink.net!trnddc05.POSTED!6c2ea718!not-for-mail
Xref: TK2MSFTNGXA01.phx.gbl microsoft.public.dotnet.languages.csharp:96289
X-Tomcat-NG: microsoft.public.dotnet.languages.csharp

I managed to narrow this down to a very simple expression. try this:

private void Bug()
{
bool b = false;
Test(3, (b || b) && b && !b);
}

private void Works()
{
bool b = true;
Test(3, (b || b) && b && !b);
}

private void Test(decimal v, bool b)
{
MessageBox.Show(v.ToString());
}

The value of the argument v in Test() is 0!

If you change the function Bug() and make bool b = true it works fine. I
checked the MSIL code and it appears that the compiler chocked on the
expression "(b || b) && b && !b" and silently truncated the previous
argument to bool (1 byte). Since the argument is a decimal it will show zero
because the truncation occurs on the decimal structure.

You can try this with any struct and any value as the first argument, the
compiler will always truncate the struct on the stack to 1 byte. If you try
with your own struct you will lose all the data after the first byte.

Strangely enough, if you remove the parenthesis "b || b && b && !b" is works
fine.

Plese don't focus on the expression, it's a simplification of a real
expression. The nastiest part is that there is no compiler error or warning,
it simply generates wrong MSIL code.

Is this a known bug? If not, how do I report this to Microsoft and to the
.NET team?

I tried in the past to report a bug with the shortcircuit operators specs
and compiler but didn't suceeded and the defect is still there.

I also tried with the latest service pack, the version shown in the .NET
Configuration module is version 1.1.4322.573.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top