Very Strange Script-Based Behavior For CFParam In ColdFusion
Sometimes, in production, I run a script that injects a "snoop" method into every cached ColdFusion component that CFDumps-out the internal Variables scope. I do this from time to time to see if there are any values showing up in the Variables scope that shouldn't be there. If so, this would typically indicate that a "local" variable is missing a Var statement and is accidentally showing up in the ColdFusion component's private scope (ie, Variables). The other day, I noticed that a few Controllers had "rc" variables defined privately. After some debugging, I narrowed it down to the CFScript-based version of the CFParam tag (ie, param).
Looking in the ColdFusion bugbase, I see that Adam Cameron, master debugger, already logged a ticket for this about a year ago. He found it in ColdFusion 9.1; however, I just tested it in ColdFusion 10 and the issue still exists. Furthermore, my experimenting demonstrates that the bug is oddly inconsistent.
To demonstrate, consider the following ColdFusion component, Thing.cfc:
<cfscript>
component
output = false
hint = "A component to help test param and arguments."
{
public any function getThis( rc ) {
param name="rc.a";
param name="rc.b";
return( variables );
}
public any function getThat( rc ) {
param name="rc.x" default="";
param name="rc.y" default="";
return( variables );
}
}
</cfscript>
Each of the two public methods accepts an optional struct named "rc." This is what you see in a lot of MVC applications; the "rc" stands for "Request Collection" or "Request Context." In each method, I am then param'ing two variables within the request collection. In one method, the param lacks both Type and Default attributes; in the other method, the param lacks only a Type attribute.
Each method then returns the internal, private Variables scope.
Ok, now, let's run some code against this ColdFusion component. In the following experiment, we invoke each method twice - once without the optional rc argument; once with it:
<cfscript>
thingOne = new Thing();
writeOutput( "RC Omitted From Request" );
writeDump( var = thingOne.getThis(), showudfs = false );
writeDump( var = thingOne.getThat(), showudfs = false );
writeOutput( "<br />" );
writeOutput( "<br />" );
thingTwo = new Thing();
writeOutput( "RC Included In Request" );
writeDump( var = thingTwo.getThis( {} ), showudfs = false );
writeDump( var = thingTwo.getThat( {} ), showudfs = false );
</cfscript>
When we run this code, we get four CFDump's of the Variables scope (two per component):
There's a number of interesting things to see in this output:
The first set of params, that only declare a name, fail to raise an exception if the given variable doesn't exist. This is the bug that Adam already logged in the bugbase.
The first param is placing a property "name" (value "rc.a") into the private variables scope.
The second param, which has the exact same structure, does not leak into the private variables scope.
If the optional "rc" argument is omitted, the "rc" auto-generated structure is placed in the private variables scope.
If the optional "rc" argument is included, the first set of params still fail to raise an exception; furthermore, the incorrect "name" property (value "rc.a") is still placed in the private variables scope even though the "closer" arguments scope now has an "rc" value.
This is some funky chicken! To help get around this quirky behavior you should probably always include a "Type" attribute in the Param tag; this way, ColdFusion will, at least raise an exception if this missing value does not have the correct type. Also, you should make sure your optional structs have default values if you're going to CFParam their properties.
Want to use code from this post? Check out the license.
Reader Comments
that's some crazy ass sh*t Ben.
i made several variations of the code to confirm that it is specifically the cfscript base param and it does appear to be exactly that. i'll spare ya the details but i made all kinds of different changes - but as soon as you use the script based param tag it acts like your example.
Shouldn't you really be referring to arguments.rc in the methods, rather than just rc? What happens if you do that?
Seb, function arguments is literally the first thing in the scope chain, so even if there were _also_ a local.rc, CF should still find arguments.rc via just "rc"... but that said, I am a fan of explicit references. :)
@Adam,
Agreed, it shouldn't be necessary. Would be interesting to see if it resolves the odd behaviour though...
I would expect the rc variable to leak into to variables scope if the argument is not passed in. CF behaviour treats optional arguments as 'UNDEFINED' if they haven't been passed in, therefore the variable 'rc' doesn't exist in any scope and created by default in the variables scope. As Seb said, it should explicitly scoped as param name="ARGUMENTS.rc.a"; That said, it should throw an exception when the variable doesn't exist..
@Seb, @Adam,
When I was using CFTags, I definitely used a lot more explicit scope references. But, since I have moved to CFScript, I have dropped most of my scope references to This, Local, Arguments, and Variables (NOTE: I still scope all other scopes ie, CGI, Cookie, Url, Form, Query). I think this stems from a strong desire to have my ColdFusion CFScript code and my JavaScript code look more alike.
@David,
Very interesting. I hadn't really considered how ColdFusion would treat an "undefined" value. That could make sense. That said, there's still a lot going wrong here - "rc.a" should still not be showing up in the variable scope when the rc argument is defined.
@ben,
Yes, the appearance of a variable called 'name' with the value 'rc.a' is quite absurd.. it's like the CFML engine fails to parse the param call and only sees a 'name="rc.a"'; variable assignment... this may be due to the fact that when param was first converted to cfscript it supported an alternative syntax..
would set default a variable called 'fish' to the value specified. and the following..
would throw an error if 'fish' was undefined or not numeric.. as of cf9 (I think) this was updated to support the name=value attribute syntax your using. The old syntax is described
see docs here under 'Tags without bodies' -> http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSc3ff6d0ea77859461172e0811cbec0999c-7ffa.html#WSA308D3F2-1B3E-454f-A001-30C0F7443203
@David,
Yeah, I can see how the diversity in syntax would definitely create some odd behavior. A little too flexible :) But, even at that, it's still strangely inconsistent. If the old syntax was leaking through, it still seems odd that it only happens to the first param instance, not the second one. Too odd!
@Ben,
That behaviour is expected. The first param statement checks for the existence of a variable called 'name' and sets it to "rc.a" then the second param statement looks for a variable called 'name' which it finds because the previous one has just created and set it. So it doesn't do anything.
I did some quick tests and it seems that whilst Railo still recognises the old syntax it doesn't have the same conflict on the name attribute.
@David,
Ah, I see what you're saying as far as why the second param didn't "appear" to do anything. Thanks for the clarification. Still, I've gone back through the problematic code (in production) and made sure that all param tags have a "type" attribute to force it into the more robust param syntax.