Tuesday 9 July 2013

Esoteric bug in argumentCollection handling

G'day:
Here's some more shoddiness / stupidity in ColdFusion. And it's nothing to do with JSON or web sockets for a change.

Consider this code:

function firstFunction(){
    writeDump(var=arguments, label="arguments recevied by firstFunction()");
    var argsForNextFunction = {
        first = arguments.first & " then updated in firstFunction() before passing on",
        third = "set by firstFunction()",
        argumentCollection = arguments
    };

    writeDump(var=argsForNextFunction, label="Arguments passed to secondFunction() & thirdFunction()");

    secondFunction(argumentCollection=argsForNextFunction);
    thirdFunction(argumentCollection=argsForNextFunction);
}

function secondFunction(first, second, third){
    writeDump(var=arguments, label="Arguments received by secondFunction()");
}

function thirdFunction(first, second, third, argumentCollection){
    writeDump(var=arguments, label="Arguments received by thirdFunction()");
}

firstFunction(first="Passed by calling code", second="Passed by calling code");

Notice how in firstFunction() I am building a struct to represent the argumentCollection passed to each of secondFunction() and thirdFunction(). I am passing the same struct to both of those latter two functions, and both simply dump their arguments.

And this is the output on both ColdFusion 9 & 10:

arguments recevied by firstFunction() - struct
FIRSTPassed by calling code
secondPassed by calling code
Arguments passed to secondFunction() & thirdFunction() - struct
ARGUMENTCOLLECTION
Arguments passed to secondFunction() & thirdFunction() - struct
FIRSTPassed by calling code
secondPassed by calling code
FIRSTPassed by calling code updated in firstFunction() before passing on
THIRDset by firstFunction()
Arguments received by secondFunction() - struct
FIRSTPassed by calling code
SECONDPassed by calling code
THIRDset by firstFunction()
Arguments received by thirdFunction() - struct
ARGUMENTCOLLECTION
Arguments received by thirdFunction() - struct
FIRSTPassed by calling code
secondPassed by calling code
FIRSTPassed by calling code updated in firstFunction() before passing on
SECONDundefined
THIRDset by firstFunction()

Notice how secondFunction() is not actually receiving the value passed into it for the first argument.

WTH?

Well what's happening is ColdFusion is seeing the argumentCollection key in argsForNextFunction, and deciding "oh, hang on... despite it clearly conflicting with the actual code, they must have meant this to be the arguments to the function, not what they actually asked for".

For Pete's sake.

And note even if ColdFusion was correct to take it upon itself to use the embedded argumentCollection substruct as an argumentCollection argument, it's still not even doing that right! Because the value for first in the argumentCollection should be trumped by the "inline" first argument value, eg:

function echo(){
    writeDump(arguments);
}

argStruct = {
    first = "set in argStruct",
    second = "also set in argStruct"
};

echo(first="overriden in function call", third="set in function call", argumentCollection=argStruct);

Result:

struct
SECONDalso set in argStruct
firstoverriden in function call
thirdset in function call

This demonstrates expected behaviour when a function receives both an argumentCollection and specific arguments: the specific argument values are given priority over any same-named arguments in the argumentCollection. This makes sense.

So basically ColdFusion has taken upon itself to do the wrong thing, and then even within the context of doing the wrong thing, manages to balls that up too.

Notice how CF actually does the correct thing in thirdFunction(), wherein I specify argumentCollection as an actual argument of the function. However this doesn't mean the behaviour when I don't specify that argument is any less wrong.

Railo, incidentally, does the correct thing here, throughout.

ColdFusion bug: 3591629.

Sigh.

--
Adam