ColdFusion Struct Assignment Catch

I solved a mystery at work today, and in the process, learned a bit about how ColdFusion struct literals work.

What I mean by a “struct literal” is the literal assignment of a struct, using JavaScript-ish notation:

<cfset myStruct = {id=17,  firstname="John", lastname="Roberts"}>

This is a lot more concise than doing

<cfset myStruct = structNew()>
<cfset = 20>
<cfset myStruct.firstname = "John">
<cfset myStruct.lastname="Roberts">

Our application makes a log entry when visitor logs out, using some information stored in session, and we’ve been getting some very sporadic crashes when this happens. The worst kind of once-in-a-blue-moon-almost-impossible to catch crashes. Here’s what was happening.

Due to the way the code is laid out, I couldn’t just write out the log when we cleared session, so I stored things in a temporary request struct that I made sure always existed:

<cfset request.logoutinfo = {attempted=false}>

This line always runs, so this key always exists. If we actually are logging out, I store more information in the structure:

<cfset request.logoutinfo = {attempted=true, visitorid=session.visitorid, firstname=session.vfname, lastname=session.vlname }>

The page then merrily runs along, until things are fully loaded, and then we do

<cfif request.logoutinfo.attempted>
<!--- Do some stuff with the first and last names --->

Works great, except once in a blue moon it wouldn’t. We’d get a crash report saying something like “Lastname is not defined in session.logoutinfo”. I couldn’t understand it, because it would only get into this code if request.logoutinfo.attempted was true, which would only happen if we set the whole structure.

We got another crash report today, and I was looking at the code again, and realized that the assignment of the struct was within a try/catch, since we don’t want to bother the user with an error if one of the session keys didn’t exist. My assumption had been that it was a single assignment statement, and that if one key failed, the whole assignment would fail.

This is not the case.

How ColdFusion actually assigns struct literals

What actually was happening is that each key is processed one by one, and if the third assignment fails because the value being used doesn’t exist, the first two keys have still been assigned. So in the example above, if session.vlname didn’t exist, request.logoutinfo.attempted would still be true (telling the page to enter the logging branch), and request.firstname would still be session.vfname. And because the whole thing was try/caught, the page steamed on, right into the iceberg.

Fixing the issue was trivial. The cfcatch block had been empty, since I’d assumed the request.logoutinfo.attempted key would have remained assigned to false. I now  reset it to false in the cfcatch. I could have left the catch empty, and moved the assignment of the “attempted” key to the end, but I felt it was clearer to set it explicitly.