Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the ColdFusion Centaur And Bolt User Group Tour (Jun. 2009) with: Ben Forta and Alison Huselid

Overriding ColdFusion's ArgumentCollection And An Extremely Weird Behavior!

Posted by Ben Nadel
Tags: ColdFusion

I have to admit, I have not used ColdFusion's ArgumentCollection all that much. And, when I have used it, it's been an all or nothing thing. In fact, I only recently learned that you could override aspects of an ArgumentCollection with additional arguments. I knew that as of the latest ColdFusion updater you could override AttributeCollection elements in ColdFusion tags, but it simply never occurred to me that the same could be accomplished with ArgumentCollection.

To test this functionality out, I set up a small ColdFusion user defined function that just CFDump's out its arguments:

  • <cffunction
  • name="OutputArguments"
  • access="public"
  • returntype="void"
  • output="true"
  • hint="I simply CFDump out my arguments.">
  •  
  • <!--- Output arguments. --->
  • <cfdump
  • var="#ARGUMENTS#"
  • label="From OutputArguments()"
  • />
  • </cffunction>

Then, I tested this using CFInvoke and CFInvokeArgument:

  • <!--- Define the argument collection. --->
  • <cfset objArgs = {
  • Naomi = "Sweet",
  • Kit = "Tough",
  • Christina = "Cocky"
  • } />
  •  
  • <!---
  • Invoke the test method using both the argument
  • collection and an overriding value.
  • --->
  • <cfinvoke
  • method="OutputArguments"
  • argumentcollection="#objArgs#">
  •  
  • <!--- Override argument. --->
  • <cfinvokeargument name="Naomi" value="Wicked hot" />
  • </cfinvoke>

As you can see here, we are passing in an ArgumentCollection attribute and then overriding one of the argument keys (Naomi) with a CFInvokeArgument. The overriding value is "Wicked hot", and in fact, when we run the code, we get this output:

 
 
 
 
 
 
Overridng Arguments In An ArgumentCollection Using CFInvoke And CFInvokeArgument. 
 
 
 

As you can see, "Wicked hot" came through as the overriding argument.

Doing this with CFInvoke seems fairly naturally since the ArgumentCollection and the CFInvokeArgument are in completely separate places. But, can this be accomplished with a standard method call as well?

  • <!--- Define the argument collection. --->
  • <cfset objArgs = {
  • Naomi = "Sweet",
  • Kit = "Tough",
  • Christina = "Cocky"
  • } />
  •  
  • <!---
  • Invoke the test method using both the argument
  • collection and an overriding value.
  • --->
  • <cfset OutputArguments(
  • ArgumentCollection = objArgs,
  • Naomi = "Insanely cute"
  • ) />

Here, I am passing in the ArgumentCollection as a named argument and then a second argument to override one of the keys (Naomi) with a new value, "Insanely cute". To me, this seems extremely counterintuitive, but, in fact, when we run this code we get the following output:

 
 
 
 
 
 
Overridng Arguments In An ArgumentCollection Using A Standard Method Call With An Accompanied Argument. 
 
 
 

As you can see, the Naomi value was successfully overridden. Honestly, I can't believe that actually worked.

This overriding is really good to know; however, as I was testing this stuff, I stumbled across an extremely odd behavior. I can't even begin to theorize on what the heck is going on here - all I can do is show you the code and a demo:

  • <!--- Define the argument collection. --->
  • <cfset objArgs = {
  • Naomi = "Sweet",
  • Kit = "Tough",
  • Christina = "Cocky"
  • } />
  •  
  • <!---
  • Invoke the test method using both the argument
  • collection and an overriding value.
  • --->
  • <cfinvoke
  • method="OutputArguments"
  • argumentcollection="#objArgs#">
  •  
  • <!--- Override argument. --->
  • <cfinvokeargument name="Naomi" value="Wicked hot" />
  • </cfinvoke>
  •  
  • <br />
  •  
  • <!---
  • Invoke the test method using both the argument
  • collection and an overriding value.
  • --->
  • <cfset OutputArguments(
  • ArgumentCollection = objArgs,
  • Naomi = "Insanely cute"
  • ) />

When you look at the code, it seems fine. Really, I am just running one method after the other - nothing suspect here. But, check out this video of me running the code in the browser:

 
 
 
 
 
 
 
 
 
 

WTF, right!?! What the heck is going on there? This has to be some sort of a bug. Has anyone else encountered this issue? I am running ColdFusion 8.0.1.

That's not the only thing I found; here's another weird behavior. I could only get this to happen when I override and argument with CFInvoke, not with a standard method call:

  • <!--- Define the argument collection. --->
  • <cfset objArgs = {
  • Naomi = "Sweet",
  • Kit = "Tough",
  • Christina = "Cocky"
  • } />
  •  
  • <!---
  • Invoke the test method using both the argument
  • collection and an overriding value.
  • --->
  • <cfinvoke
  • method="OutputArguments"
  • argumentcollection="#objArgs#">
  •  
  • <!--- Override argument. --->
  • <cfinvokeargument name="Naomi" value="Wicked hot" />
  • </cfinvoke>
  •  
  • <br />
  •  
  • <!--- Dump out original argument collection. --->
  • <cfdump
  • var="#objArgs#"
  • label="Original Argument Collection"
  • />

Nothing special going on here - just making the method call, which will CFDump out its arguments, then I CFDump out the original argument collection. When we run this code, we get the following output:

 
 
 
 
 
 
Overridng Arguments In An ArgumentCollection Using CFInvoke And CFInvokeArgument Results In The Original ArgumentCollection Object Being Updated Permanently. 
 
 
 

As you can see, by overriding a key in the ArgumentCollection (Naomi) using CFInvokeArgument, ColdFusion is actually altering the original ArgumentCollection structure. This seems very strange to me; and, since I could not get the same behavior using a standard method call, I have to assume that this too is some sort of bug.

Using the ArgumentCollection in ColdFusion is a powerful way to dynamically and conditionally build a collection of arguments; but, realize that overriding those arguments can be a dangerous game that, as seen in the video above, can yield unpredictable results. Until these "bugs" are ironed out, I would suggest NOT overriding arguments. Rather, Duplicate() your ArgumentCollection and then override the appropriate values before making the method call.




Reader Comments

Ben,

I'm guessing it's because you're using the same struct (objArgs) in both calls. The 'override' is actually changing the value of the Naomi key within the struct itself, and since structs are passed by reference, when the override changes the value, it's changed everywhere it's used. I guess, then, it comes down to what was the final value assigned to the key.

You might try your test by passing a copy of objArgs into each method, and see what happens then. I'm stretching on all of this, but it would make sense.

Reply to this Comment

@Steve,

Hmm, I guess then using a method call acts in a fundamentally different way:

<cfset OutputArguments(
. . . . ArgumentCollection = objArgs,
. . . . Naomi = "Insanely cute"
. . . . ) />

This does not override the original objArgs structure. The difference in behavior is what made me think this was a bug. However, you can also do this with no problem:

<cfset OutputArguments(
. . . . ArgumentCollection = objArgs,
. . . . Naomi = "Insanely cute",
. . . . Naomi = "Stupid sexy",
. . . . Naomi = "Wicked gorgeous"
. . . . ) />

In that case, whatever last named argument was passed in is the one that take precedence inside of the method call.

But, if we just step back and think about the "intent" of an argument collection - I feel like having it permanently alter the argumentCollection key feels wrong.

Of course, that feeling is based on AttributeCollection, which to me, seems like a great way to centralize values like Mail and DSN configuration (that can then be overridden when needed. Maybe ArgumentCollection is not indended to be used in that way at all.

That aside, the video demo is still bananas :)

Reply to this Comment

Yep, I think Cutter got it. If you dump objArgs at the end, you see it was modified. We all know structs are passed by ref to a UDF, but I know I didn't even consider that in terms of argumentCollection.

One interesting note - when you dump objArgs, it is modified, but it does NOT 'flip' like your test output does.

Also - cfdump isn't involved at all here. I did a test where I built a copy of your udf that did cfreturn arguments, and I did a plain output of result.naomi. It showed the same 'flip' behavior.

Reply to this Comment

Get this crap. If you use duplicate in your method calls:

<cfinvoke
method="OutputArguments"
argumentcollection="#duplicate(objArgs)#">

...

<cfset OutputArguments(
ArgumentCollection = duplicate(objArgs),
Naomi = "Insanely cute"
) />

The objArgs struct is NOT modified (good!) but you still see flipping. I think I can accept the objArgs being modified if you don't use a duplicate() call... but the flipping definitely seems like a bug.

Reply to this Comment

I don't think we should accept objArgs being modified if you don't use Duplicate() though, because if Ben wanted the struct to be modified he would have done it with a simple cfset, which would be intuitive. The additional arguments are really unrelated to the data in the struct. And there are 2 different behaviours present for what should be the same operation; one for direct function calls and one for cfinvoke.

I think there are 2 bugs here... a threading issue with cfinvoke (the "flipping"), and cfinvokeargument modifying the argumentCollection - it should use a copy internally, not modify the original struct.

Reply to this Comment

We will have to agree to disagree Justin. By convention, structs passed to methods are passed by reference. As I said, it didn't occur to me to think of it in regards to argumentCollection, but I think it makes sense now that I see it, and it is consistent.

Reply to this Comment

@Justin, Ray,

Ray, I see what you are saying about the pass-by-reference thing; but, this just doesn't feel intuitive enough to justify that. The intent is *not* to update the original structure. The *intent* is to override it in a one-time-only fashion. Of course, this is based on the idea that you might want to use an argumentCollection structure more than once (which was probably not the original intent when it was designed).

I think we need to look at AttributeCollection. If the same behavior is produced via the use of AttributeCollection, then I would say that this was thought out. To me, this seems like parallel situations:

<cfmail attributeCollection="#mail#" server="mailserver" />

.. and overriding an argumentCollection with a CFInvokeArgument.

Reply to this Comment

I jsut tested. If you call a custom tag and use attribteCollection, and you mod the struct in the CT, it does indeed mod the calling struct. I'd call that consistent. ;)

Reply to this Comment

What about a tag that isn't a custom tag? A tag whose processing we have no control over?

The problem still stands that this does not modify the struct:

<cfset OutputArguments(ArgumentCollection = objArgs, Naomi = "Insanely cute"
) />

But cfinvoke + cfinvokeargument *does* modify the struct. That is inconsitent!

Reply to this Comment

It looks like this does not hold water with a core ColdFusion tag (with AttributeCollection). I just ran this:

<cfset mail = {
. . . . to = "ben@xxxxxxxx.com",
. . . . from = "info@yyyyyyy.com",
. . . . subject = "Pre-defined subject line.",
. . . . format = "html"
. . . . } />

<cfmail attributecollection="#mail#" subject="Override Subject">
. . . . Hey there
</cfmail>

<cfdump var="#mail#" />

When I dump out the "mail" struct, the Subject is still:

"Pre-defined subject line."

At least with the CFMail tag, the overriding attribute does not alter the original attribute collection.

Reply to this Comment

@Ray,

I just switched my code to call:

<cf_mail attributecollection="#mail#" subject="Override Subject">

(switched it to a custom tag) and it still does not update the original structure. What was the code you were testing to get the original structure to change?

Reply to this Comment

@Ray,

Ah, gotcha. To me, this "Feels" like the way it should work. Of course, argumentCollection is much older than attributeCollection, and, I think there is a slightly different intent. Even so, something doesn't feel Kosher.

Reply to this Comment

Heh, you reversed it again. ;) ArgumentCollection is new. ;) And to be fair, it isn't too new. The ability to use it in core cftags is new. The ability to use it in cfinvoke, udf isn't so new.

Reply to this Comment

I've used argumentCollection like this for quite a while. I pass in a struct of args to a method, the method changes them internally. Most of these methods don't even have returns, I'll reference the struct later with the changes made to it by the method.

I can see why attributeCollection, especially with custom tags, would operate differently, as most things within custom tags (outside of the request scope) are encapsulated. Variables scoped variables, created within a custom tag, are not available outside the tag. Seems pretty clear cut, from a difference stand point, but I guess that's just me.

Reply to this Comment

Hah, weird... After I did a test I'm seeing the same "flipping" behaviour as Jon on the function call rather than the cfinvoke, and I'm on 8.0.0 not 8.0.1. And with regard to custom tags I'm finding that they don't modify the original struct!

Confirmed though is that cfinvoke definitely modifies the original struct whereas the function call does not.

Reply to this Comment

I just wanted to add that if you explicitly define arguments within the cffunction tag with a cfargument tags there seems to be no flip flopping.

Reply to this Comment

@PJ,

I just confirmed that you are correct. If you define the arguments with CFArgument tags then the swapping does not take place.

Reply to this Comment

I looked at the code and this is indeed a bug. When you pass both argument collection and a named parameter, we make a hashmap of these named parameters where one name is argumentCollection and another is "Naomi". The parameters are then merged to build the final arguments to be used for invocation.

since hashmap has no order defined, the final merged data can override the value from either named param or from argumentCollection passed. I will log a bug for this.

Reply to this Comment

@Rupesh -

Glad to see someone from the Adobe team picked up on this so quickly. Am I correct in that you won't be breaking the current functionality? That being, a struct passed in via argumentCollection will still be passed by reference, with the ability to manipulate that reference internally (to the method) and still seeing the result externally (outside the method)?

Reply to this Comment

@Rupesh: What about the difference in behaviour between calling a method directly and using cfinvoke when using an argumentCollection + named arguments? That's the 2nd bug that popped up here. The named arguments when using cfinvoke should not modify the argumentCollection struct directly.

Reply to this Comment

@Justin -

I would disagree. The purpose of passing the named argument, in conjunction with the argumentCollection, is to override that particular key value. In my mind, since we know the struct is passed by reference, then intended behavior would be for the struct key to be changed to the override value. If that isn't the action a developer would intend, then they should be passing a copy of the struct in the argumentCollection, not the original.

Reply to this Comment

@Steve,

I disagree that this is the "intent" of the developer. Are you telling me that the very first time you did this, you expected that behavior? OR, is this something that you discovered, and then altered the way you used it to leverage this behavior?

Obviously, I cannot speak for you, but, if you DID alter your coding to leverage this behavior, then this behavior is not really the intended one.

Plus, the fact that this does not happen for AttributeCollection, which really should follow the same rules, makes me think this is not the intended behavior.

Think about it this way - imagine that ColdFusion had a built-in function for every tag such that in CFML you could use CFMail and in CFScript, you could use CFMail(). Now, in one, you would use ArgumentCollection and in the other, you would use AttributeCollection - both with the same intent. Do you think it seems right that these two would act differently based on the calling environment.

And here's another point to be considered - when you add totally "new" argument to the method call (one that is not in the original argument collection), do you think it makes sense to add this new argument to the argumentCollection struct? Now, we're no longer talking about overriding, but rather adding. To me, this makes even less sense.

Reply to this Comment

@Steve: I think the point is to only override a parameter for that function call, and that's the way it works in all similar cases (direct function calls, attributeCollection in built-in tags, attributeCollection in custom tags) except for cfinvoke. If you want to modify the struct it should be done directly, before the function is called.

This bug doesn't really have anything to do with structs being passed by reference because the bug occurs before we even get to the body of the function. The struct itself isn't being passed into the function for manipulation within the function as a struct. Once inside the function they are just normal variables that belong to the arguments struct.

Obviously, I would agree that if a struct belongs to the argumentCollection struct then it should be passed by reference, not copied, as that is how it would ordinarily behave if passed directly as a parameter to the function.

Reply to this Comment

@Ben -

Please, call me Cutter. Only my mama calls me Steve;)

I've always expected this behavior with argumentCollection. I figured out long ago the consequences of forgetting that structs are passed by reference, and leveraged that reference within my coding practices. It's much like passing objects around as arguments. If you look at the underlying code of many of the frameworks, a bean object will be passed into a DAO and manipulated. There's no return set on these methods, as the object is passed by reference, so changes to the object are available outside the method automatically without having to explicitly return the object (look at the code produced by Illudium for another example of this). I've never used the named argument override of a key, but that would be the behavior I would expect since argumentCollection takes a struct, which would therefore be passed by reference.

I would not expect the same behavior with attributeCollection, only because custom tags have always been a bit of a black box. Variable references changed within a custom tag are not easily available outside of that tag unless you explicitly make them so. While that means the struct-by-reference rule would not work with attributeCollection, it would be in line with how the ATTRIBUTES scope has always worked with custom tags.

Reply to this Comment

@Cutter,

While I can agree that understanding structs are passed by reference is important, I still think there is a huge difference between passing in something as an explicit argument (ex. Bean explicitly passed into a DAO) vs. a collection of data that is supposed to be distributed as arguments. Maybe it's just me, that feels quite different.

But, while this is a theoretical example, I think that:

<cfmail attributecollection="#args#">

.. and...

<cfset CFMail( argumentCollection = args ) />

should act the same way and, to me, have the exact same intent.

I guess we will just have to agree to disagree :)

Reply to this Comment

I've just done another test with cfinvoke + argumentCollection... It's definitely bugged :) Let me just post some code and a screenshot...

Reply to this Comment

@Justin: I rarely use cfinvoke. I write quite a bit using cfscript, so I prefer CreateObject(). For both (on the occasions I do use cfinvoke) I almost always use argumentCollection, unless I'm only passing in a single value.

@Ben: I guess I've just spent too much time with JavaScript, where an object is basically a struct with functions as additional keys. I've always treated a struct kinda like an object without methods. I also look at CFC methods and UDFs as being a different animal than tags and custom tags. Methods and UDFs are singular functions, whereas tags and custom tags are themselves complete objects, with their own internal methods. As far as passed-by-reference is concerned, I would almost consider that attributeCollection itself breaks functionality, and that it's written the way that it is to maintain behavior as it has always previously been perceived: a holdover of the days before CF ran on top of Java.

As you said, we'll just have to agree to disagree:) I would be curious to hear what Sean or Hal might say on the discussion though...

Reply to this Comment

Ok, here's the code:
http://rafb.net/p/ZOdKGg82.html

And here's a screenshot of the output:
http://img114.imageshack.us/my.php?image=cfinvokebughp7.png

The expected output from each of the first 4 tests *should* be: original, FUNCTION PARAMETER, INSIDE FUNCTION, original.

(The 5th test was just included for good measure).

The second test suffers from the "flipping" bug that Rupesh is looking into. If you refresh a few times you will see the output change from what is expected to the bugged output.

All tests here prove that modifying an argument inside a function does not modify the original struct if the arguments were passed in by using an argumentCollection, except for the 4th test which is bugged!

The 4th test proves there is a bug when using cfinvoke + argumentCollection + named arguments. It *should* be the equivalent of the second test, the difference is that it's using cfinvoke instead of a direction function call, and it's output *should* match the first 3 tests.

But, as you can see, the named parameter immediately modifies the original struct that was passed in by argumentCollection. Then in the body of the function we modify that same value (which we've already proven does not affect the original struct). Then upon returning to the calling code, you can see the original struct contains the static value from the named parameter.

This is a bug! It's contradictory to the behaviour of a direct function call, and it's counter-intuitive to have a (somewhat) static function parameter just magically modify a struct.

I hope that clears up any confusion :)

Reply to this Comment

Oops, here's a more direct link to the screenshot of the output, the other one doesn't seem to work properly:
http://img114.imageshack.us/img114/1938/cfinvokebughp7.png

Reply to this Comment

@Justin,

Good stuff, but at this point, we are now getting too concerned about the implementation (I think a sign that something, somewhere isn't right). Is the original ArgumentCollection translated into the ARGUMENTS scope? Or are they supposed to be the same thing?

Clearly, they cannot be the same thing as the ARGUMENTS scope is a different type of object (with very special struct/array-like behavior). But, then it gets perhaps a bit confusing that modifying an argument inside the function does not modify the argumentCollection object.

This is actually very interesting! It creates two different intents. This:

<cfset ARGUMENTS.Value = "Ben" />

... acts different as:

<cfset ARGUMENTS.Value.Name = "Ben" />

In the first, the value being passed through is "simple" and is therefore being passed by value, not reference. The second is a struct and is therefore passed by reference, which would create a change in the original argumentCollection.

This makes sense from a micro-view-point (just worrying about what it means to be passed by reference vs. passed by value). HOWEVER, when you take further into account that the argumentCollection is being passed as a "single concept", then it stops making sense that only one of these CFSet actions would modify the original structure and not both of them.

The disconnect here is the "translation" of argumentCollection into ARGUMENTS scope. Something here feels wrong and that is because the concept of the "collection" is being separated from the concept of "translation". It's like we have a mish-mash of the concepts; I think we need to go one way or the other.

Reply to this Comment

@Ben: I think the confusion is the second guessing of how argumentCollection should work and the assumption that it is "passed into" a function as a struct...

In my code, the cfset inside the function modifies the original struct, it can't. The only modification that is occuring is caused by the buggy behaviour that has been exhibited when using it with cfinvoke + cfinvokeargument. This happens before we ever get to the function body (as the test case proves).

If we think about the intention of the feature, argumentCollection is a method of allowing developers to define a bunch of arguments once and use them multiple times. It's a way to pass a number of arguments to a function without having to type out each argument manually even if the arguments rarely change. It increases maintainability and code reuse.

The additional benefit of specifying named argument(s) which may already exists in the argumentCollection is to override the value *without* concern for what is in the struct, because the explicitly named arguments take precendence. This is only logical, because if the argumentCollection took precedence then it becomes *less* useful. Also, you would never expect this to directly modify the original argumentCollection struct, you would only expect it to override the value that ends up being used for that argument in the function.

So the notion that the argumentCollection is a structed that is passed by reference into the function is false, and that's where the confusion has come from. In reality it is each of the indices in the struct that are passed into the function as arguments (and it's quite possible that some of *those* could be complex data types passed by reference - I haven't tested it yet as I'm on a machine that doesn't have CF8, but that's how I'd expect it to work).

My 2c anyway. I'd be interested to hear if others think differently :)

Reply to this Comment

Very interesting, I use argumentCollection and attributeCollection a lot and can't believe I haven't seen this (I dont use cfinvoke much at all)!

I just hope I remember it when I do though so I don't spend hours blinking at the code wondering wtf.

Question: Anyone know a way to access the original structure passed in as the value of Attribute/ArgumentCollection? I need to differentiate between path-through metadata and declared.

Reply to this Comment

@Brett,

You got me?? I think since the translation happens pre-ARGUMENTS (we think), you might not be able to determine this.

Reply to this Comment

@Ben - thanks. I was hoping it might have been some hidden variable, or a result of some javaFoo.

I've had the issue on my list a while. I'll post if I find a way, if not I'll have to do things a bit differently.

Reply to this Comment

My perspective is the ArgumentCollection and AttributesCollection aren't really the same as a user defined argument in your tag, they are more like a short hand way to pass arguments/attributes that are predefined by the application without typing each in every place in the code that it is used, I think that logic would dictate that the directly specified value in the call overrides the value in the struct in regards to the internal operation, but the struct itself really shouldn't be touched, infact i think that the ArgumentCollection should be a shallow copy to make the behavior inside the function/tag consistant. I would expect all of these to behave the same:

<cfset Args={A="2",B="7",C="Apples"}>
<cfinvoke method="MyFunc" argumentcollection="#Args#" C="Cherries">
<cfdump var="#Args#">

<cfset Args={A="2",B="7",C="Apples"}>
<cfinvoke method="MyFunc" A="#Args.A#" B="#Args.B#" C="#Args.C#" C="Cherries">
<cfdump var="#Args#">

<cfset Args={A="2",B="7",C="Apples"}>
<cfset MyFunc(ArgumentCollection = Args, C="Cherries")>
<cfdump var="#Args#">

<cfset Args={A="2",B="7",C="Apples"}>
<cfset MyFunc(A=Args.A,B=Args.B,C=Args.C, C="Cherries")>
<cfdump var="#Args#">

I think the should all use C="Cherries" inside the function but the dump should be C="Apples", also any modification of Arguments.C in the function shouldn't effect the value of Args.C outside the function.

Reply to this Comment

I'd agree with Ben and Justin: the implementation should non-destructively override the argument for that call only. In my mind, it should work functionally identical to this:

<cfset StructAppend(arguments, arguments.argumentCollection, "false") />

Its how I would expect it to work.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.