Unexpected Structure Behavior with {}

A gearYesterday I ran into an interesting issue with one of my applications that lead to an even more interesting discovery about how CF8 executes the {} notation for creating structures.

The Problem

The issue that I ran into is that in my application's startup and reinitialization function, I had a declaration like this:

<cfset application.config = {
    email = "myemail@mycompany.com",
    title = "My Application Title"
} /
>

I setup and got running with the standard error handlers to email me issues, and everything seemed fine. A few days later, I get an error email letting me know that "element email is not defined in application.config"... Well, ok, I forgot to use a lock around my read, so thats my fault, but the more I thought about it why would email ever be undefined? Wouldn't CF look at the {} and evaluate it as a block?

The Tests

In order to find out how this was happening, I needed to setup some tests cases. First thing was to "slow things down" so that I could see how ColdFusion was evaluating the structure declaration. To do this I setup a test without the new notation:

<!--- Application.cfc --->
<cfcomponent output="false">
    <cfset this.name = "AppLockTest" />
    
    <cffunction name="onApplicationStart">
        <cfset onReinitialization() />
    </cffunction>    
    
    <cffunction name="onRequestStart">
    
        <cfparam name="url.reinit" default="0" />
        
        <cfif url.reinit>
            <cflock scope="application" timeout="5" type="exclusive">
                <cfset onReinitialization() />
            </cflock>
        </cfif>
    
    </cffunction>
    
    <cffunction name="onReinitialization">
    
        <cfset application.struct = StructNew() />
        <cfset sleep(2000) />
        <cfset application.struct.server = RandRange(10,100) />
        <cfset sleep(2000) />
        <cfset application.struct.datasource = RandRange(10,100)/>
        <cfset sleep(2000) />
        <cfset application.struct.users = RandRange(10,100) />
    
    </cffunction>
    
</cfcomponent>

And I setup a basic test page in the index.cfm:

<!--- index.cfm --->
<cfdump var="#application#">

If you open up a browser to this app, it will take about 6 seconds to initialize the first time, and if you open up another browser you can tell one two reinit and then refresh the other to watch as each part gets added to the structure. Thats neat, but we already expect this behavior. Lets update our application to something more like the original setup:

<!--- Application.cfc 2 --->
<cfcomponent output="false">
    <cfset this.name = "AppLockTest2" />
    
    <cffunction name="onApplicationStart">
        <cfset onReinitialization() />
    </cffunction>    
    
    <cffunction name="onRequestStart">
    
        <cfparam name="url.reinit" default="0" />
        
        <cfif url.reinit>
            <cflock scope="application" timeout="5" type="exclusive">
                <cfset onReinitialization() />
            </cflock>
        </cfif>
    
    </cffunction>
    
    <cffunction name="onReinitialization">
    
        <cfset application.struct = {
            server         = setupValue(),
            datasource     = setupValue(),
            users         = setupValue()
        } /
>

    
    </cffunction>
    
    <cffunction name="setupValue">
        <cfset sleep(2000) />
        <cfreturn RandRange(10,100) />
    </cffunction>
    
</cfcomponent>

I couldn't in-line the sleep() calls with this example, so I pulled the value return into a separate function to net the same result. If you open to browsers and try this again, you'll see something interesting... it behaves exactly like the first example. I'd expect it to execute as a block level declaration, similar to if I had done the following:

<cfcomponent output="false">
    <cfset this.name = "AppLockTest4" />
    
    <cffunction name="onApplicationStart">
        <cfset onReinitialization() />
    </cffunction>    
    
    <cffunction name="onRequestStart">
    
        <cfparam name="url.reinit" default="0" />
        
        <cfif url.reinit>
            <cflock scope="application" timeout="5" type="exclusive">
                <cfset onReinitialization() />
            </cflock>
        </cfif>
    
    </cffunction>
    
    <cffunction name="onReinitialization">
    
        <cfset application.struct = createStruct(
            server         = setupValue(),
            datasource     = setupValue(),
            users         = setupValue()    
        ) /
>

    
    </cffunction>
    
    <cffunction name="setupValue">
        <cfset sleep(2000) />
        <cfreturn RandRange(10,100) />
    </cffunction>
    
    <cffunction name="createStruct">
        <cfset var x = StructNew() />
        <cfloop collection="#arguments#" item="arg">
            <cfset x[arg] = arguments[arg] />
        </cfloop>
        <cfreturn x />
    </cffunction>
    
</cfcomponent>

In this example I've faked a "block" level evaluation of a struct declaration with createStruct().

The Conclusion

The only conclusion I can draw from this is that ColdFusion does not execute {} declarations as block level elements (and probably doesn't do [] as blocks either). I would not have expected this behavior... is this something you would have expected?

 

Comments

Ben Nadel's Gravatar Jon, reading your blog post made me realize that this bug could actually be leveraged:

http://www.bennadel.com/index.cfm?dax=blog:1556.vi...

Not saying it should ( in fact it should NOT ), only that this just occurred to me.
Rick O's Gravatar That is interesting that it serializes the assignments, instead of creating a temporary L-value and then doing the assignment last.

That's a bug. That's not sane behavior. There's no reason to not fix that.
Ben Nadel's Gravatar @Rick,

If not a bug, at least a poor implementation of the intended use.
Jon Hartmann's Gravatar @Ben: Yeah I might have actually accidental "leveraged" this bug without thinking about it I mean if you convert code that sets things line by line into a single collected declaration, you might not even notice, since it worked before and after the update.

I've also confirmed that this happens if you are building an array with [something, something] as well, so basically it looks like both methods compile to line-by-line declaration rather then processing as a block.
Jon Hartmann, July 2011

I'm Jon Hartmann and I'm ColdFusion guru, Javascript fanatic, and UX/UI evangelist. Stay right here to read my technical posts as I grapple with mysterious error messages, user interface design questions, and all things baffling and irksome about programming for the web. Learn more about me.

Post a job. Find one. authenticjobs.com

Interested in becoming a sponsor? Contact me.