Monday, April 16, 2007

Oh Captain My Forms

Today I finally committed support for one of the last remaining features in vbnc: the My namespace.

It was easier than expected, hadn't it been for one little problem I had with a little close friend of mine: Reflection.Emit. I stumbled upon the single worst issue I had during last year's SoC, an issue it took me four days to work around. Note: work around, not fix.

At that time I was able to work around it, since it was happening with the compiler, now this wasn't an option anymore, since it was the generated My code that was causing this.

The problem is quite simple: anytime you have a generic type parameter on a method with a constraint, some other generic type parameter (on a completely unrelated type, not method) might fail emission (with a nice TypeLoadException, claiming that the constraint isn't met).

The workaround I used last year is quite simple: remove all constraints, but as mentioned, that isn't an option anymore, so I started investigating. Unfortunately I lost the test case I cooked up last year, but it didn't take me long to recreate one this time. (Last year I had to start with the entire compiler and comment it out piece by piece...)

Namespace OhCaptainIMixedUpTheTypeParameters
    Class C1(Of Y)
    End Class
    Class C2(Of Z)
        Inherits C1(Of Z)
    End Class
    Class C3
        Sub M1(Of A As New)()
        End Sub
        Sub M2(Of B)()
        End Sub
    End Class
End Namespace

Looks quite innocuous, doesn't it?

On MS this fails with: TypeLoadException: GenericArguments[0], 'B', on 'TypeParameter1.C1`1[A]' violates the constraint of type parameter 'A'.

Now read the exception again and try to relate the A and B to the Z and Y.

Of things you can try to make this compile includes changing the order (for example make C3 the first class in the file), or moving the methods to either C1 or C2.

With this test case in hand I was quite certain it was a bug in Reflection.Emit, but I wasn't entirely sure either. So I added code to vbnc that dumps out how it emits everything in VB code, and got a nice 90-line source file that when compiled and run nicely reproduces the problem.

The conclusion is that it's not a bug in vbnc, which is quite bad (I can't fix it). So everybody that wants to compile any VB code with generic type method parameters, be warned: if the compiler quits with a weird TypeLoadException, you know who to blame.

The best part is that it Mono does not suffer from this bug, so if you want your project to compile no matter what, you'll have to use Mono :)

PD: Already filed a bug with MS (link), and they seem to have confirmed it's a bug in Reflection.Emit.

Update 01/07/2007: Fixed bug-link.
Update 21/09/2011: Fixed html for VB code.

Ain't Numeric

Today I learned something: don't trust a compiler. I repeat: don't ever trust a compiler. It might be doing some magic behind your back.

A while ago I was assigned a bug, at first it looked really easy, but no matter what I tried I couldn't reproduce it, and I was positive the reporter was experiencing the problem. So I just left the bug standing there, and of course, eventually it hit me.

Quiz: If you compile the following source code, what will the IL look like?

Class AintNumeric
    Shared Sub Main
    End Sub
End Class

The problem was that this printed "False" on Mono and "True" on MS. I compiled the code with vbnc, ran the code on Mono and it printed "True". I ran it on MS and it printed "True". I disassembled the program to see if the compiler was doing something wrong, and it wasn't. I put Console.WriteLines in MS.VB.Information.IsNumeric and they all confirmed that the function was working like it should. Just to check I compiled the program with vbc, ran it on MS and it printed "True".

You might have seen what I missed, but I'll tell you anyway: I didn't run the MS compiled program on Mono. My bad, the reported of the bug explicitly wrote that he did it, but I honestly didn't think it would change anything. vbnc was compiling everything correctly, wasn't it? 

Well no, and now here is the answer to the quiz:

.method public static void Main() cil managed {
    .maxstack 8
    L_0000: ldstr "0"
    L_0005: call bool     [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Versioned::IsNumeric(object)
    L_000a: call void [mscorlib]System.Console::WriteLine(bool)
    L_000f: ret

The Microsoft VB8 compiler changes every method call to Microsoft.VisualBasic.Information.IsNumeric to Microsoft.VisualBasic.CompilerServices.Versioned.IsNumeric. And of course, that function was buggy in Mono...

Now you may think about it and find this is some really bad behaviour, but it's all because of backwards compatibility. When VB 8 came out they added support for unsigned types, which is the reason behind this: The old version of IsNumeric returns false for unsigned numeric types... and since that couldn't change it because it would break backwards compatibility, and they didn't want to add another function handling the new data types (and telling everybody they had to use the new funcion), they created some compiler magic.

And yes, the bug is fixed and vbnc does the same magic now.