Friday 5 October 2012

How 4 can equal 3 (or possibly 5) in ColdFusion

G'day:
This was the article I was meaning to write today, but I got sidetracked with the IDE survey thing (I've got 30 replies so far: thanks!), and this took a while longer to nut out than I expected.  Plus work gets in the way sometimes (it's lunch time now... my tech leads reads this stuff, so I figure I had better mention that ;-).

OK, so thanks to my new CF bugs RSS feed I now know all about the new CF bugs that get raised as they happen.  This has been helpful for me twice already (in the space of 48h), so I'm pleased with that.

Today I got notified of 3341284 being raised, which is very similar - but not the same, as it turns out - as a bug we're experiencing in CF9.  So it got my focus.

The short version is that when one uses the argumentCollection to pass arguments around, it can screw up your arguments scope.  So this is quite serious.  This problem first cropped up in CF9 (not CF10 as per the bug report), and persists in CF10.  It's fine in CF8.  And in Railo.  And in OpenBD.


As part of testing this I've found another bug, which itself is quite curious.  First of all, here's some code that demonstrates both problems:

The output for this (on CF9) is as follows (quickly eyeball it, but I'll summarise it below the output to save you some "WTH?" time):

baseline

inner args:
struct.copiedBefore.d = 4
struct.copiedBefore - struct
D4
a1
b2
c3
struct.copiedAfter.d = 4
struct.copiedAfter - struct
a1
b2
c3
structCount is 4: NO
structCount(): 5
structKeyList(): a,b,c
toString(): {D={{D, 4}},a={{a, 1}},b={{b, 2}},c={{c, 3}}}
serializeJson(): {"D":4,"c":3,"b":2,"a":1}

withoutDummyTest

inner args:
struct.copiedBefore.d =4
struct.copiedBefore - struct
D4
a1
b2
c3
struct.copiedAfter.d = 4
struct.copiedAfter - struct
D4
a1
b2
c3
structCount is 4: YES
structCount(): 4
structKeyList(): D,a,b,c
toString(): {D={4},a={1},b={2},c={3}}
serializeJson(): {"D":4,"a":1,"b":2,"c":3}

withDummyTestSeparateArgs

inner args:
struct.copiedBefore.d = 4
struct.copiedBefore - struct
D4
a1
b2
c3
struct.copiedAfter.d = 4
struct.copiedAfter - struct
D4
a1
b2
c3
structCount is 4: NO
structCount(): 5
structKeyList(): D,a,b,c
toString(): {D={{D, 4}},a={{a, 1}},b={{b, 2}},c={{c, 3}}}
serializeJson(): {"D":4,"a":1,"c":3,"b":2}


OK, so what's going on?

Basically, what we should expect to see in ALL these results is this:

structCount is 4: YES
structCount(): 4
structKeyList(): D,a,b,c
toString(): {D={4},a={1},b={2},c={3}}
serializeJson(): {"D":4,"a":1,"b":2,"c":3}

The code is doing this:
  • passing three arguments: a=1, b=2, c=3 into a function.
  • Copies the arguments into a local struct.
  • Passes the arguments (not the copy of them) into another function - dummyTest() - as an argumentCollection.
  • dummyTest() does nothing at all.  Nothing.
  • After that function call, it takes a second copy of the arguments.
  • It then sets a fourth key/value into both of those copied structs, d=4
  • It outputs this D key from the first struct to prove it's there
  • It dumps the whole first struct to prove that the D key is there
  • It outputs this D key from the second struct to prove it's there
  • It dumps the whole first struct to prove that the D key is there: but it's not there
  • And lastly outputs some telemetry on that second struct, as follows:
    • whether the structCount() is four (which it should be): no it isn't!
    • what the count actually is: apparently it's 5
    • the result of a structKeyList() call: a,b,c (where's the D key?  And how is that five elements?)
    • a serialisation of the struct using Java's toString(): there's the D key, no worries.
    • a serialisation of the struct using serializeJson: no D key.
  • then the code repeats the same process, using some variations, to try to work out what's going on:
    • the same process with the call to dummyTest() commented-out.  This test works fine: D is there all the time, and the count is always 4.
    • the last test is the same process except passing individual arguments to dummyTest(), instead of the argument collection.  This almost works, except for some reason the structCount() of the second struct is still five. Although there seems to be only four elements (and there is only four elements, so that would be our expectation).
OK, so back to "WTH?" now, I think.

I think there are (at least) two bugs here.  The bug as raised - "where the hell has the D key got to?" - as well as another bug relating to ColdFusion's "mixed messages" as far as the structCount() goes.  I'll raise that one shortly (done: 3341767).

On CF8, the problem with the D key does not occur: it's always there, so that's good.  But the problem with the structCount() is there.

And on CF10 the results are the same as CF9.

As I said earlier, Railo and OpenBD just get it completely right.

One thing to notice in this which might contribute to things is this.  Look closely at the result of the toString() call.  For the tests where the structCount() is off, the serialisation of the struct comes out as this:

{D={{D, 4}},a={{a, 1}},b={{b, 2}},c={{c, 3}}}

However when the count is correct, it's this:

{"a":1,"b":2,"D":4,"c":3}

Don't worry about the ordering changing (that's to be expected with a struct), just note the double-up of the curly braces.  What's going on there? And why with one is the equals sign used for the assignment, but for the other, the colon? What's going on?

Weird.

And that's it for lunchtime, so back to work.

--
Adam