Monday 1 July 2013

Boxes: Sneaky Buggers

Last year I wrote at fair length about the hidden dangers of Monkey's auto-boxing:
http://pointlessdiversions.blogspot.co.nz/2012_03_01_archive.html

You'd think that having spent some time explaining it all that I'd be proofed against the schemes of evil boxes. Unfortunately not. Heed my tale of woe.

Cubic Bastards

I have a class "Path" that deals with, well... paths. For convenience I give each path a human-readable string identifier and this gets passed in on construction:

Method New ( id:String )

This has been in place for quite a while with no problems, which you'd expect, because there's nothing really wrong with it. Then, the other day I wrote some code that contained a simple error. I had a couple of fields in a class that manipulated paths, let's call it PathManipulator for the purposes of this post:

Class PathManipulator
    Field currPath:Path
    Field currPathID:String
    ...

The separate path and path id field were to allow the path to be set via the ID string and retrieved from a map if desired. In addition I wrote a setter method:

Method SetCurrPath:Void( id:String )
    currPath = id
End

And there's the error. I accidentally set the path reference rather than the string reference.

So, I wrote this and some more code and compiled and it all ran okay. Then after adding some more code I started getting an Array Out Of Bounds exception crash. The crash was in the Path class, not in my PathManipulator code and looked like something was somehow clearing the internal points array. Cutting a long story short I spent several hours trying to trace where this array clear was occurring before working out that the source of the problem was the setter method above.

This was my fault though, right? Well, yes and no. It's an error, no doubt, but an error that maybe shouldn't be so difficult to spot. In the setter I'm assigning a String to a Path reference. Why does that even compile? It compiles because I provided a constructor for my Path class that takes a String value as input. Monkey's compiler sees this and helpfully converts the line:

currPath = id

becomes

currPath = New Path(id)

So, no compile error, just a silent replacement of the currPath reference with an uninitialised path instance that has an empty points array resulting in me looking in completely the wrong place for the problem. The boxes got me again.

They're out to get you. To get all of us.

The ability to define your own classes with auto-boxing is certainly a handy thing, but this incident just further convinces me that it's too easy to be caught out by the way it currently functions. I feel like it should require some explicit notice to the compiler that you intend for a class to be auto-boxing rather than have any constructor that takes a primitive assumed to be for that purpose.

Be careful out there.