Sunday 13 July 2014

ColdFusion: some built-in functions aren't actually functions. It seems.

G'day:
This caught me out the other day, but I've just now had a change to have a look at it some more. In ColdFusion (as distinct from Railo) some built-in CFML functions such as writeLog(), throw() and writeDump() aren't actually functions. Or aren't implemented properly if they are.

Last week I wrote that article around my case/when sample code (I still need to continue that one): "Some more TestBox testing, case/when for CFML and some dodgy code". The code for it eventually became Railo-only because ColdFusion choked on some of my syntax. As a short-cut in my argument validation, I had this code:

var validateCondition = function(condition){
    condition ?: throw(type="MissingArgumentException")
    isBoolean(condition) || isCustomFunction(condition) || isClosure(condition) ? true : throw(type="InvalidArgumentException")
}

Basically I have two checks:

  • whether the condition argument had been passed at all
  • and if it had, whether it was legit: either a boolean, or a UDF of some description.
I thought this was quite a nice use of both the null-coalescing operator and the ternary operator: having the fail condition actually fail. IE: to raise an exception. 

But no... on ColdFusion, I get this instead:

Method throw with 1 arguments is not in class coldfusion.runtime.CFPage.

The error occurred in blogExamples/coldfusion/functions/notfunctions/throwFromBlog.cfm: line 5
3 : function f(){
4 :  var validateCondition = function(condition){
5 :   condition ?: throw(type="MissingArgumentException");
6 :   isBoolean(condition) || isCustomFunction(condition) || isClosure(condition) ? true : throw(type="InvalidArgumentException");
7 :  };

(I have just recreated / refactored that code for the sake of this article; the error was the same in the original code).

This error is completely bogus. Here's a rewrite to not use throw() in an inline expression like that:

if (isNull(condition))  throw(type="MissingArgumentException");
if (!(isBoolean(condition) || isCustomFunction(condition) || isClosure(condition))) throw(type="InvalidArgumentException");

All I've done here is to use an if() statement instead of the two flavours of ?: operator.

I can pare the code back further to demonstrate where something starts going amiss.

x = throw(type="MissingArgumentException", message="The correct exception was thrown");

This is nonsense code, but is syntactically valid. I'd expect throw() to be a void function, so x would not be assigned a value, but that does not invalidate the syntax. Yet I get this error:

Parameter validation error for the THROW function.

A built-in ColdFusion function cannot accept an assignment statement as a parameter, although it can accept expressions. For example, THROW(d=a*b) is not acceptable.

That's obviously bullshit, as if I take the assighment of x away, the code works fine:

throw(type="MissingArgumentException", message="The correct exception was thrown");

The correct exception was thrown

The error occurred in blogExamples/coldfusion/functions/notfunctions/throwFromBlog.cfm: line 18
16 : }
17 : 
18 : throw(type="MissingArgumentException", message="The correct exception was thrown");
19 : </cfscript>

The issue seems to be down to poor implementation of the inbuilt ColdFusion functions which take name/value pairs as arguments: throw(), writeDump(), writeLog(). None of them work properly as functions.

Here are reasonably legit use-cases of doing so:

structKeyExists(URL, "test")
    ? writeLog("Received #URL.test#")
    : writeOutput("Oi. Where's the test attribute?");

Errors with:

Method writeLog with 1 arguments is not in class coldfusion.runtime.CFPage.

The error occurred in blogExamples/coldfusion/functions/notfunctions/writeLog.cfm: line 4
2 : // writeLog.cfm
3 : structKeyExists(URL, "test")
4 :  ? writeLog("Received #URL.test#")
5 :  : writeOutput("Oi. Where's the test attribute?");
6 : </cfscript>

And this example using writeDump():

structKeyExists(URL, "debug") ? writeDump([URL,form,CGI]) : false;

gives the same error.

I realise this is all pretty edge-case-y stuff. But what I don't get is how it is that Adobe have gone out of their way to implement these "functions" differently that all the other functions in the language. That's really a very special flavour of bone-headedness indeed.

Needless to say... Railo does the job properly, and each of these functions actually are implemented as functions, and all work as one would expect.

Anyway, I can't say I lost any sleep over it, but it's just one more reason to shake one's head in disbelief at ColdFusion. And make me pleased about Railo. Because they do their jobs thoroughly and properly.

I've raised a bug for this: 3788414.

--
Adam