Welcome to Atalasoft Community Sign in | Help

Compiler Bugs

In a mature product, such as a compiler, you don't expect to find compiler bugs too often, but today I managed to find one in the  Microsoft managed C++ compiler.

To start off with, here is an enum and a little class definition:
    public enum MyEnum
    {
        steve,
        glenn,
        lou,
        dave,
        bill,
        sy
    }

    public abstract class EnumGiver
    {
        public abstract MyEnum GiveOne();

        public abstract MyEnum[] GiveMany();
    }


In this class, we want to implement the two methods in a subclass.  In my case, I needed to implement this method in managed C++, so I created the following:
public __gc class ExportFromC : public EnumGiver
    {
    public:
        static ExportFromC() {
            __crt_dll_initialize();
        }

        virtual EnumDefinition::MyEnum GiveOne()
        {
            return MyEnum::glenn;
        }

        virtual EnumDefinition::MyEnum GiveMany() __gc[] {
            EnumDefinition::MyEnum some[] = __gc new MyEnum[2];                    some[0] = MyEnum::bill;
            some[1] = MyEnum::dave;
            return some;
        }
    };
which is very straightforward.  Now for fun, I also made a chunk of C# to do the same thing (and when I say, "for fun" I mean that the C++ code was not at all doing what I expected it to do and I needed a baseline for comparison):
            MyEnum[] tester = new MyEnum[2];
            tester[0] = MyEnum.bill;
            tester[1] = MyEnum.dave;

which is equivalent code to the C++ implementation of GiveMany.  The problem I had was that if I instantiated ExportFromC and called it's GiveMany method, I couldn't view the values in the debugger, nor could I actually use them anywhere.  I opened up each section of code in ILDASM and looked at what was going on.  First, the C#:
  IL_0000:  ldc.i4.2   // prepare to allocate a 2 entry array
  IL_0001:  newarr     [EnumDefinition]EnumDefinition.MyEnum
  IL_0006:  stloc.0    // save the array in a local
  IL_0007:  ldloc.0    // reload it
  IL_0008:  ldc.i4.0   // store the constant 4 into element 0
  IL_0009:  ldc.i4.4
  IL_000a:  stelem.i4
  IL_000b:  ldloc.0    // reload it
  IL_000c:  ldc.i4.1   // store the constant 3 into element 1
  IL_000d:  ldc.i4.3
  IL_000e:  stelem.i4

Easy enough.  Now here's what the C++ compiler generated for GiveMany():
  IL_0000:  ldnull     // set the array variable to null
  IL_0001:  stloc.0
  IL_0002:  ldc.i4.2   // allocate two elements
  IL_0003:  newarr     [mscorlib]System.Enum
  IL_0008:  stloc.0    // etc.
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.0
  IL_000b:  ldc.i4.4
  IL_000c:  stelem.i4
  IL_000d:  ldloc.0
  IL_000e:  ldc.i4.1
  IL_000f:  ldc.i4.3
  IL_0010:  stelem.i4
The problem is that the C++ compiler generated a newarr instruction with the wrong type.  Yikes!  In other words, instead of returning EnumDefinition::MyEnum __gc [], we're returning System::Enum __gc[], which is an incompatible type.  This is compiler bug number one.

I went looking for another way to allocate this type so that I could have the correct code.  I was hoping that managed C++ would let me use the __asm directive to write IL directly into a method, but apparently __asm implies x86.  The next idea was to try to construct this object via reflection.

Next step was to take the type of the object, pull it's constructor and invoke it--all of which I managed to do successfully.  The problem is that the result of ConstructorInfo::Invoke is Object * and apparently, you can't cast an Object * to EnumDefinition::MyEnum __gc[], even though I was pretty sure I had one of those in my greedy little hands.

Another approach is to take the code, compile it in C++ then disassemble it, search for the newarr instruction with the incorrect type, substitute the correct type and reassemble it.  I can't tell you how wrong it feels to do that.  We have a complicated enough build as it is, so there is no reason to make it any worse.  My solution was to build a protected static method into the base abstract C# to allocate an array for me and then I call that from managed C++.  This is still disgusting, but a compiler bug is a compiler bug and other reasons require that this code be written in managed C++, so this is the least distateful of our options.
Published Monday, December 11, 2006 4:44 PM by Steve Hawley

Comments

Friday, December 15, 2006 8:40 AM by Steve Hawley

# Followup from Microsoft

It looks like this bug is fixed by a service pack released in 8/2006.  We need to assess the risk of installing this service pack, but I'll be sure to check for a fix to this bug.

Anonymous comments are disabled