Wednesday 3 July 2013

Reserved words? Or not? Make up yer mind

G'day:
What a busy CFML day today (ed: it's now the following day... I didn't get this finished last night) has been. And it seems it just keeps on giving. Here's a quirky one that has bitten me on the butt before, and has now bitten the butt of someone on Stack Overflow too.


This is an extract from the docs for CFML's reserved words:

The following list indicates words you must not use for ColdFusion variables, user-defined function names, or custom tag names. Although you can safely use some of these words in some situations, you can prevent errors by avoiding them entirely.
  • Any name starting with cf. However, when you call a CFML custom tag directly, you prefix the custom tag page name with cf_.
  • Built-in function names, such as Now or Hash
  • Scope names, such as Form or Session
  • Operators, such as NE or IS
    [...]

I've highlighted two bits of note:
  1. I really don't like this "it'll work sometimes, but sometimes it won't. But we won't tell you which" sort of thing. They're either reserved or they're not, surely?
  2.  And this is from the operators page in the docs:
    Decision operators
    The ColdFusion decision, or comparison, operators produce a Boolean True/False result. Many types of operation have multiple equivalent operator forms. For example, IS and EQ perform the same operation. The following table describes the decision operators:
    OperatorDescription
    IS
    EQUAL
    EQ
    Perform a case-insensitive comparison of two values. Return True if the values are identical.
    IS NOT
    NOT EQUAL
    NEQ
    Opposite of IS. Perform a case-insensitive comparison of two values. Return True if the values are not identical.
    CONTAINSReturn True if the value on the left contains the value on the right.
Right. So that's a long-winded way of identifying that CONTAINS is a reserved words. Well, you know, sometimes.

And, sometimes, it bites you on the bum. Here are two - basically identical - CFCs:

<cfcomponent output="false">

    <cffunction name="contains">
        Hi from TagBased.contains()<br>
    </cffunction>

    <cffunction name="notContains">
        Hi from TagBased.notContains()<br>
    </cffunction>

</cfcomponent>

component {

    function contains(){
        writeOutput("Hi from ScriptBased.contains()<br>");
    }

    function notContains(){
        writeOutput("Hi from ScriptBased.notContains()<br>");
    }

}

The only real difference here is that the message is different in each function. Granted on is in script and one in tags, but that doesn't matter as far as the language vagaries goes, one would think.

However the script one does not compile, instead giving this error:

Invalid CFML construct found on line 3 at column 9.

ColdFusion was looking at the following text:
function

(although it's actually having issue with contains, not function).

The tag version compiles fine. And, I hasten to add: the script version compiles fine on Railo. The thing is, if this was really a "reserved word" issue, surely it would simply not be possible to have a method called contains. I think it's more a glitch in the ColdFusion CFScript code parser.

OK, so we'll shelve the script-based CFC and run with the tag one. Next issue...

<cfset o = new TagBased()>
<cfset o.contains()>

This runs fine, declaring "Hi from TagBased.contains()".

Now I'm upping the ante slightly:

<cfset objects = [
    new TagBased(),
    new TagBased(),
    new TagBased()
]>

<cfset objects[1].notContains()>
<cfset objects[1].contains()>

So no CFScript to be seen. This again runs fine on Railo - so they figure it's valid CFML (as do I) - however ColdFusion again gives a compile error with the contains() call. If I comment-out the contains() call, the notContains() call works fine. It's just ColdFusion getting prissy about the word "contains".

Well the issues isn't actually with contains here, as this revised code demonstrates:

<cfset objects = [
    new TagBased(),
    new TagBased(),
    new TagBased()
]>
<cfset object = objects[1]>
<cfset object.notContains()>
<cfset object.contains()>

This works OK. So it's the combination of bracket-notation and contains which the parser doesn't like. This doesn't sound like an issue with reserved-word`d-ness to me, it just smells of slackness in the code parser.

Here's another even simpler example of this:

<cfset contains = "value">
<cfdump var="#variables#">

This errors on ColdFusion, but is fine on Railo. And if one simply specifies variables.contains instead of referring to it unscoped, then it works on ColdFusion too.

Bottom line: there's definitely some erratic behaviour in ColdFusion here to be aware of. However Railo demonstrates that the code parser can actually be clever enough to not have a problem with this, so I think Adobe should lift their game and get this sorted out. There's no need to have rules like this if they're a) not policed uniformly or predictably; b) not necessary anyway.

Footnote: this isn't all just theoretical. We interact with third-party (Java-based) systems which have methods "contains", so it's a pain in the butt to have to work around this. Also - due to a shortcoming in the way it works - its' not possible to mock a method called "contains" using Mockbox (I'll let the ColdBox bods know about this. Brad: you now know about this ;-), due to the way Mockbox implements the mocks.

Anyway, that was all not very exciting, but it was a topical continuation of my day yesterday of seemingly being a ColdFusion bug magnet. I'm over that now, so hopefully I don't spot / become victim of any more bugs today...

--
Adam