Strange ___IMPLICITARRYSTRUCTVAR Behavior In ColdFusion
While James England and I were working on Dig Deep Fitness, we came across a rather mysterious ColdFusion behavior. The local memory leak detection mechanism started reporting the existence of an unexpected variable called, ___IMPLICITARRYSTRUCTVAR0
. I'd never seen this before; but Google pointed me to something Adam Cameron mentioned it on an old Adobe ColdFusion forum post from 2012. Apparently implicit struct and array notation has been creating these hidden variables since ColdFusion 9.
Demonstration of Variable Behavior
First, let's take a look at what ColdFusion is doing. This behavior only seems to manifest when the implicit struct or array notation is used 1) inside a Function execution and 2) within a ternary operator. As such, we're going to define an immediately-invoked function expression (IIFE) which executes two nonsense ternary operators. Then, we're going to output the variables
scope.
<cfscript>
(() => {
var arrayValue = false
? "yay"
: []
;
var structValue = false
? "yay"
: {}
;
for ( key in variables.keyArray() ) {
// Note: wrapping output in array because dumping-out a struct will hide any
// key that contains the substring "___IMPLICITARRYSTRUCTVAR".
writeDump([ key, variables[ key ] ]);
}
})();
</cfscript>
After the two ternary operators are executed, I output the keys in the variables
scope. When I do this, I'm wrapping the key-value pairs in an array since ColdFusion will actively hide any struct entry in which the key contains the substring, ___IMPLICITARRYSTRUCTVAR
. This is probably why I've never noticed this before.
When we run this Adobe ColdFusion 2023 code, we get the following output:
Notice that ColdFusion has created two hidden variables relating to the implicit data structure notation: ___IMPLICITARRYSTRUCTVAR0
and ___IMPLICITARRYSTRUCTVAR1
. These variables contain the result of the implicit data structure expression execution.
These Variables Are Not Thread Safe
These implicit struct and implicit array variables are stored in the variables
scope. When this is done within a ColdFusion template or a ColdFusion custom tag, this is safe because the variables
scope only lasts as long as the template execution (more or less). But, when this is done within a persisted ColdFusion component (CFC), it can become a problem.
When a ColdFusion component is instantiated and cached for the lifetime of a ColdFusion application, the variables
scope of said component becomes a shared memory space that can be accessed by parallel requests and parallel threads. This can create a dangerous environment if you don't take precautions. And, it can create an even more dangerous environment when you don't even know that a shared variable is being created behind the scenes (especially one that is actively hidden from CFDump
and WriteDump()
output).
Let's take a look at how the dangerous behavior can manifest. We're going to use the asynchronous iteration features of ColdFusion to launch a number of parallel threads. These threads will then use implicit struct notation to define and mutate a local variable:
<cfscript>
arrayNew( 1 )
// Create a collection that is large(ish).
.resize( 1000 )
.set( 1, 1000, 1 )
// Iterate over the collection using PARALLEL THREADS.
.each(
() => {
var value = false
? "yay"
: { count: 0 } // CAUTION: Creates a shared variable.
;
// Increment LOCAL value.
value.count++;
},
true, // Parallel iteration.
20
)
;
// Output the private page variables.
for ( key in variables.keyArray() ) {
writeDump([ key, variables[ key ] ]);
}
</cfscript>
As you can see, the value
variable is declared as local variable to the iteration operator, which should make it safe to subsequently mutate. However, when we run this Adobe ColdFusion 2023 code, we get the following error:
As some point, during the parallel iteration, we get the error Element COUNT is undefined in VALUE
. This is when we go to increment the value.count
key, which should be local to the iterator function and therefore safe to mutate.
What I assume is happening is that the results of the ternary operator are being mechanised as series of assignments:
Create empty struct using
___IMPLICITARRYSTRUCTVAR0
value, stored in thevariables
scope.Append
count:0
entry to___IMPLICITARRYSTRUCTVAR0
.Assign
___IMPLICITARRYSTRUCTVAR0
tovalue
variable, stored in thelocal
scope.
Any time you have a multi-step process that touches shared memory, the process is inherently unsafe for parallel computing unless locking precautions are applied. Of course, we didn't know that this was happening; so, we didn't expect this code be unsafe.
Aside: even the
++
and--
operators are unsafe to use when it comes to a shared memory space.
What's likely happening is that just before one of the iterations goes to increment the .count
key, a parallel iteration thread has just overwritten the shared ___IMPLICITARRYSTRUCTVAR0
variable, thereby clobbering the .count
key for the other thread.
To work around this bug, all you have to do is move the implicit struct expression out of the ternary operator. One way to do that is to replace the ternary operator with an if
/else
control flow:
<cfscript>
arrayNew( 1 )
// Create a collection that is large(ish).
.resize( 1000 )
.set( 1, 1000, 1 )
// Iterate over the collection using PARALLEL THREADS.
.each(
() => {
// By moving the implicit struct OUT of the ternary operator and into an
// if/else block, we remove the implicit variable creation.
if ( false ) {
var value = "yay";
} else {
var value = { count: 0 };
}
// Increment LOCAL value.
value.count++;
},
true,
20
)
;
// Output the private page variables.
for ( key in variables.keyArray() ) {
writeDump([ key, variables[ key ] ]);
}
</cfscript>
This time, when we execute this Adobe ColdFusion 2023 code, it executes without error. And, the resultant page is blank since no ___IMPLICITARRYSTRUCTVAR0
variable is created in the variables
scope.
Crazy stuff! I don't know if this is limited to the ternary operator; but, it's the only place that I've seen this behavior manifest itself.
Want to use code from this post? Check out the license.
Reader Comments
I just tried copy/pasting this code into the Adobe ColdFusion 2025 beta over on https://cffiddle.org/ and the same behavior and error can be seen.
One of the tickets that Adam Cameron files in the bug tracker outlined some of this behavior:
https://tracker.adobe.com/#/view/CF-3043459
So, what we're seeing in CF 2021+ might be a regression (I didn't try it in 2018). I will open a new ticket for tracking.
I've filed a new bug: https://tracker.adobe.com/#/view/CF-4225079
This is incredibly insightful and very well explained. When I get random errors I can't explain, I usually just throw my hands up and hope it resolves itself 🤣
But now you've given me another possible angle to consider. Fortunately, I don't do much parallel processing.
@Chris,
Sometimes, it's fun to dig deep into what is going wrong. Of course, I don't write about the times I failed to figure it out 😜
re: parallel processing, even if you're not doing much stuff with async iteration, multiple users making a request to the same page is still parallel processing. If this workflow were part of a cached CFC instance, for example, and multiple users were hitting a Function that had the implicit struct / ternary operator combination, you'd still be able to trigger the same edge case.
That's actually the scarier concept. If the issue were to always throw an error, that's not so bad. But, the real red flag is if there's any chance that one user's data might accidentally stored into a struct that is then made available to a different user, that could have very bad consequences (and would be essentially impossible to debug since you'd have no idea this was actually the cause).
I'm not saying that I could even reproduce that cross-user issue; but the fact that two threads affect each other in an unexpected way make me think it might be possible.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →