views:

506

answers:

6

Update: I have filed a bug report with Microsoft Connect, please vote for it!

Update 2: Microsoft have marked the bug report as fixed

Posted by Microsoft on 18/08/2010 at 17:25

This bug will be fixed in a future version of the runtime. I'm afraid it's too early to tell if that will be in a service pack or the next major release.

Since upgrading to VS2010 I'm getting some very strange behavior with the 'is' keyword.

The program below (test.cs) outputs True when compiled in debug mode (for x86) and False when compiled with optimizations on (for x86). Compiling all combinations in x64 or AnyCPU gives the expected result, True.

All combinations of compiling under .NET 3.5 give the expected result, True.

I'm using the batch file below (runtest.bat) to compile and test the code using various combinations of compiler .NET framework.

  • Has anyone else seen these kind of problems under .NET 4.0?
  • Does everyone else see the same behavior as me on their computer when running runtests.bat?
  • #@$@#$??
  • Is there a fix for this?

test.cs

using System;

public class Program
{
    public static bool IsGuid(object item)
    {
        return item is Guid;
    } 

    public static void Main()
    {
        Console.Write(IsGuid(Guid.NewGuid()));
    }
}

runtest.bat

@echo off

rem Usage:
rem   runtest         -- runs with csc.exe x86 .NET 4.0
rem   runtest 64      -- runs with csc.exe x64 .NET 4.0
rem   runtest v3.5    -- runs with csc.exe x86 .NET 3.5
rem   runtest v3.5 64 -- runs with csc.exe x64 .NET 3.5

set version=v4.0.30319
set platform=Framework

for %%a in (%*) do (
  if "%%a" == "64" (set platform=Framework64)
  if "%%a" == "v3.5" (set version=v3.5)
)

echo Compiler: %platform%\%version%\csc.exe
set csc="C:\Windows\Microsoft.NET\%platform%\%version%\csc.exe"

set make=%csc% /nologo /nowarn:1607 test.cs
rem CS1607: Referenced assembly targets a different processor
rem This happens if you compile for x64 using csc32, or x86 using csc64

%make% /platform:x86
test.exe
echo  =^> x86

%make% /platform:x86 /optimize
test.exe
echo  =^> x86 (Optimized)

%make% /platform:x86 /debug
test.exe
echo  =^> x86 (Debug)

%make% /platform:x86 /debug /optimize
test.exe
echo  =^> x86 (Debug + Optimized)

%make% /platform:x64
test.exe
echo  =^> x64

%make% /platform:x64 /optimize
test.exe
echo  =^> x64 (Optimized)

%make% /platform:x64 /debug
test.exe
echo  =^> x64 (Debug)

%make% /platform:x64 /debug /optimize
test.exe
echo  =^> x64 (Debug + Optimized)

%make% /platform:AnyCPU
test.exe
echo  =^> AnyCPU

%make% /platform:AnyCPU /optimize
test.exe
echo  =^> AnyCPU (Optimized)

%make% /platform:AnyCPU /debug
test.exe
echo  =^> AnyCPU (Debug)

%make% /platform:AnyCPU /debug /optimize
test.exe
echo  =^> AnyCPU (Debug + Optimized)

Test Results

When running the runtest.bat I get the following results on my Win7 x64 install.

> runtest 32 v4.0
Compiler: Framework\v4.0.30319\csc.exe
False => x86
False => x86 (Optimized)
True => x86 (Debug)
False => x86 (Debug + Optimized)
True => x64
True => x64 (Optimized)
True => x64 (Debug)
True => x64 (Debug + Optimized)
True => AnyCPU
True => AnyCPU (Optimized)
True => AnyCPU (Debug)
True => AnyCPU (Debug + Optimized)

> runtest 64 v4.0
Compiler: Framework64\v4.0.30319\csc.exe
False => x86
False => x86 (Optimized)
True => x86 (Debug)
False => x86 (Debug + Optimized)
True => x64
True => x64 (Optimized)
True => x64 (Debug)
True => x64 (Debug + Optimized)
True => AnyCPU
True => AnyCPU (Optimized)
True => AnyCPU (Debug)
True => AnyCPU (Debug + Optimized)

> runtest 32 v3.5
Compiler: Framework\v3.5\csc.exe
True => x86
True => x86 (Optimized)
True => x86 (Debug)
True => x86 (Debug + Optimized)
True => x64
True => x64 (Optimized)
True => x64 (Debug)
True => x64 (Debug + Optimized)
True => AnyCPU
True => AnyCPU (Optimized)
True => AnyCPU (Debug)
True => AnyCPU (Debug + Optimized)

> runtest 64 v3.5
Compiler: Framework64\v3.5\csc.exe
True => x86
True => x86 (Optimized)
True => x86 (Debug)
True => x86 (Debug + Optimized)
True => x64
True => x64 (Optimized)
True => x64 (Debug)
True => x64 (Debug + Optimized)
True => AnyCPU
True => AnyCPU (Optimized)
True => AnyCPU (Debug)
True => AnyCPU (Debug + Optimized)

tl;dr ##issue.

+3  A: 

Other than a few nops, reflector indictes the only differences are in the IsGuid method:

DEBUG:

.method public hidebysig static bool IsGuid(object item) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: isinst [mscorlib]System.Guid
    L_0007: ldnull 
    L_0008: cgt.un 
    L_000a: stloc.0 
    L_000b: br.s L_000d
    L_000d: ldloc.0 
    L_000e: ret 
}

RELEASE:

.method public hidebysig static bool IsGuid(object item) cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: isinst [mscorlib]System.Guid
    L_0006: ldnull 
    L_0007: cgt.un 
    L_0009: ret 
}

I'm not fluent enough to explain these differences but this is a community wiki answer so maybe somebody else can enlighten us?

Changing the method to make it generic works around this (possible bug?)

public static bool IsGuid<T>(T item)
{
    return item is Guid;
}

As does forcing it into a local varible (but it must be used in the method to prevent the optimizations kicking in):

public static bool IsGuid(object item)
{
    bool a = item is Guid;
    a.ToString();
    return a;
}
Daniel Renshaw
I wonder if it has something to do with the JIT compiler? I have more questions that answers at the moment :)
Jacob Stanley
The IL is fine. What you want to compare is the jitted code, since it produces different results with different jitters. This looks like a jit bug.
Eric Lippert
To look at the jitted code the best thing to do is to write a program that pauses after the code you want to inspect has run. Run it, then when it is paused, attach the debugger. The reason for this rigamarole is because the jitter knows when a debugger is attached and can generate easier-to-debug code if there is one. If you want to know what its doing when there is no debugger attached then you have to trick it.
Eric Lippert
+2  A: 

Here's my results on XP SP3 (x86):

>runtest
Compiler: Framework\v4.0.30319\csc.exe
False => x86
False => x86 (Optimized)
True => x86 (Debug)
False => x86 (Debug + Optimized)
False => AnyCPU
False => AnyCPU (Optimized)
True => AnyCPU (Debug)
False => AnyCPU (Debug + Optimized)

>runtest v3.5
Compiler: Framework\v3.5\csc.exe
True => x86
True => x86 (Optimized)
True => x86 (Debug)
True => x86 (Debug + Optimized)
True => AnyCPU
True => AnyCPU (Optimized)
True => AnyCPU (Debug)
True => AnyCPU (Debug + Optimized)

The interesting point about this result is that the .NET 4 target fails on AnyCPU (running as x86) but works on AnyCPU (running as x64).

Stephen Cleary
Followup: Doing an "is int" test always works, regardless of target/platform.It also always works for my own empty value type.It also always works for "is Guid" if the parameter is "new Guid()".At this point, I'm suspecting the Guid.NewGuid() implementation of the .NET 4.0 32-bit runtime.
Stephen Cleary
Stephan agreed var a = Guid.NewGuid(); Console.Write(IsGuid(a)); seems to work.
AJ
+1 32-bit OS results
Jacob Stanley
+3  A: 

That's not cool. I think you should file a bug. (connect.microsoft.com)

Also, this seems to work (I only tested for your fail case though):

    public static bool IsGuid(object item)
    {
        return item.GetType() == typeof(Guid);
    }
Mark Synowiec
+5  A: 

To answer your last question you can add the MethodImpl attribute with the option MethodImplOptions.NoInlining to your IsGuid method as an workaround to fix the problem.

I just made a simple test of switching between Debug and Release configuration for x86 on the .NET 4.0 and this seems to resolve the problem. I haven't yet run your runtests.bat though.

You should also submit an issue in Connect if one is not yet submitted and link it from your question.

João Angelo
+1 Interesting that NoInling fixes the issue. It's not really an option for us as there are probably hundreds of such cases in our codebase and tracking them down will be difficult.
Jacob Stanley
`MethodImpl` doesn't work in my case. I'm doing the `is` check inside an event handler and inconsistency I'm seeing isn't with `struct` but with a proper class (that crosses an AppDomain boundary).
asbjornu
@asbjornu, given the differences you should ask a new question with all the details.
João Angelo
@João, yea I will once I'm able to wrap my head around exactly what's going on.
asbjornu
+4  A: 

I worked up a similar example that fails the same way:

using System;
using System.Runtime.CompilerServices;

public class Program {
  static void Main() {
    Console.Write(Verify(Test.Create()));
    Console.ReadLine();
  }
  //[MethodImpl(MethodImplOptions.NoInlining)]
  static bool Verify(IDisposable item) {
    return item is Test;
  }
  struct Test : IDisposable {
    public void Dispose() { }
    public static Test Create() { return new Test(); }
  }
}

It is a JIT optimizer bug. Can't quite put the finger on it, it optimizes the code heavily. But it looks to me like it gets in trouble when it optimizes the boxing conversion away. Pretty serious bug, frankly.

Hans Passant
A: 
Mike