I came across a bug in code that is only reproduced when the code is built with optimizations enabled. I've made a console app that replicates the logic for testing (code below). You'll see that when optimization is enabled 'value' becomes null after execution of this invalid logic:
if ((value == null || value == new string[0]) == false)
The fix is straight forward and is commented out below the offending code. But... I'm more concerned that I may have come across a bug in the assembler or perhaps someone else has an explanation of why value gets set to null.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace memory_testing { class Program { sta tic void Main(string[] args) { while(true) { Console.Write("Press any key to start..."); Console.ReadKey(); Console.WriteLine(); PrintManagerUser c = new PrintManagerUser(); c.MyProperty = new string[1]; } } } public class PrintManager { public void Print(string key, object value) { Console.WriteLine("Key is: " + key); Console.WriteLine("Value is: " + value); } } public class PrintManagerUser { public string[] MyProperty { get { return new string[100]; } set { Console.WriteLine("Pre-check Value is: " + value); if ((value == null || value == new string[0]) == false) { Console.WriteLine("Post-check Value is: " + value); new PrintManager().Print("blah", value); } //if (value != null && value.Length > 0) //{ // new PrintManager().Print("blah", value); //} } } } }
The normal output should be:
Pre-check Value is: System.String[] Post-check Value is: System.String[] Key is: blah Value is: System.String[]
The buggy output is:
Pre-check Value is: System.String[] Post-check Value is: Key is: blah Value is:
My Env is a VM running Windows Server 2003 R2 with .NET 3.5 SP1. Using VS2008 Team System.
Thanks,
Brian
Yes, your expression fatally confuses the JIT optimizer. The generated machine code looks like this:
if ((value == null || value == new string[0]) == false) 00000027 test esi,esi ; value == null? 00000029 je 00000075 0000002b xor edx,edx ; new string[0] 0000002d mov ecx,6D913BD2h 00000032 call FFD20BC8 00000037 cmp eax,esi ; (value == new string[0]) == false? 00000039 je 00000075 { Console.WriteLine("Post-check Value is: " + value); 0000003b mov ecx,dword ptr ds:[03532090h] ; "Post-check value is: " 00000041 xor edx,edx ; BUGBUG not null! 00000043 call 6D70B7E8 ; String.Concat() 00000048 mov esi,eax ; 0000004a call 6D72BE08 ; get Console.Out 0000004f mov ecx,eax 00000051 mov edx,esi 00000053 mov eax,dword ptr [ecx] 00000055 call dword ptr [eax+000000D8h] ; Console.WriteLine()
The bug occurs at address 41, the optimizer has concluded that value will always be null so it directly passes a null to String.Concat().
For comparison, this is the code that's generated when JIT optimization is turned off:
Console.WriteLine("Post-check Value is: " + value); 00000056 mov ecx,dword ptr ds:[03342090h] 0000005c mov edx,dword ptr [ebp-8] 0000005f call 6D77B790
The code got moved, but do note that at address 5c it now uses the local variable (value) instead of null.
You can report this bug at connect.microsoft.com. The workaround is simple:
if (value != null) { Console.WriteLine("Post-check Value is: " + value); new PrintManager().Print("blah", value); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With