Skip to main content
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Jared Rypka-Hauer
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Jared Rypka-Hauer

Unexpected Variable Assignment Using Function LocalMode Modern With Nested Closures In Lucee 5.3.2.77

By
Published in Comments (2)

Last week, I took a look at how the Function localmode feature in Lucee makes it safer to dynamically render CFML templates. In response to that post, John Pansewicz told me that he ran into an issue using localmode with nested Functions (for which he filed a ticket). It's one of those situations where the code is running "according to spec"; but, it's doing so in a way that is counter to what you might expect. As such, I thought it would be fun to dig a little deeper into JP's issue, along with some possible work-arounds, using Lucee 5.3.2.77.

In my previous localmode explorations, I was explicitly setting localmode="modern" on one of my Functions. But, this is not the only way to define the localmode in Lucee. You can also define it in your Application.cfc configuration such that your desired localmode will be applied to all Functions in your CFML Application:

component
	output = false
	hint = "I define the application settings and event handlers."
	{

	this.name = hash( getCurrentTemplatePath() );

	// By using "modern" localmode, any unscoped variable assignment will be applied to
	// current Function's local scope (never to the Variable's scope).
	this.localmode = "modern";

}

By setting this.localmode, we are implicitly configuring the default behavior of every Function in the Application. Now, let's look at why this was causing an issue for JP:

<cfscript>

	echo( "[A] Value: " & test() );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public string function test() {

		var value = "Original";

		(() => {

			// CAUTION: Because the entire Application is running in localmode "modern",
			// this "unscoped assignment" will be applied to the current Local scope,
			// which traps it inside of this closure.
			value = "Changed";

		})();

		return( value );

	}

</cfscript>

As you can see, all we're doing here is invoking a Function that turns around and invokes a Closure (as an IIFE - Immediately Invoked Function Expression). The Closure then tries to update a variable that is stored within the parent's local scope. And, when we run this CFML code, we get the following output:

[A] Value: Original

As you can see, the value that is returned from the test() function is unaffected by the Closure execution. That's because unscoped variable assignment within the Closure body is being applied to the local scope of the Closure, not to the pre-existing variable in the parent Function. After all, Closures are Functions. As such, they abide by the Application-wide localmode settings that we have in our Application.cfc file.

Now, assuming that you can't turn off the localmode setting in the Application.cfc file, there are a few ways to get around this issue. The first way is to simply override the localmode setting for the Closure itself:

<cfscript>

	echo( "[B] Value: " & test() );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public string function test() {

		var value = "Original";

		(function() localmode = "classic" { // <=== We are overriding LOCALMODE.

			// Since the entire Application is running in localmode "modern", in order
			// for this assignment to affect the local variable of the parent context,
			// we have to override the LocalMode, setting it back to "classic". This
			// will allow Lucee to apply the assignment to the "expected" variable.
			value = "Changed";

		})();

		return( value );

	}

</cfscript>

As you can see, in this case, we're switching from the Fat-arrow syntax to the traditional Function expression so that we can add the localmode="classic" directive to the Function signature. Even though the default behavior of the ColdFusion Application is to run in "modern" mode, we can still override that default behavior for specific Functions and Closures.

And, when we run this CFML code, we get the following output:

[B] Value: Changed

As you can see, the unscoped variable assignment in our Closure is getting applied to the local scope of the parent Function - just as we would "expect" it to.

Another way to get around this issue is to scope the variable assignment. Remember, localmode only applies to unscoped assignments; so, if we can put a scope in front of our assignment, then localmode no longer comes into play:

<cfscript>

	echo( "[C] Value: " & test() );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public string function test() {

		var value = "Original";

		// Create an "alias" for the Local scope of the current function.
		var context = local;

		(() => {

			// By using the "alias" of the parent function to define the variable
			// assignment, it allows us to "scope" the assignment. So, even though
			// "context" is just a reference to the parent function's Local scope, it's
			// enough to bypass "modern" mode.
			context.value = "Changed";

		})();

		return( value );

	}

</cfscript>

As you can see, in order to define a "scope" for my variable assignment, I'm just creating an alias for the local scope of the parent Function. Then, I can use this alias to bypass the localmode behavior. And, when we run this CFML code, we get the following output:

[C] Value: Changed

As you can see, by using the context. scope for the variable assignment within the Closure, I am able to affect the value of the original variable.

I think localmode is a very interesting feature; and I certainly think that the "modern" mode makes is easier to generate static HTML sites in ColdFusion. But, at this time, I wouldn't use it as an Application-wide setting. It doesn't quite dove-tail with the way that I traditionally think about Function execution. Instead, I'll leave my Lucee CFML applications in "classic" mode; and then, enable "modern" mode in the few places that I think it makes sense.

Want to use code from this post? Check out the license.

Reader Comments

448 Comments

Yes. I agree. I would probably only use:

localMode="modern"

In selective functions, like those that might contain:

cfinclude

Especially, if I am a new developer on a team, tasked with scoping spaghetti legacy functions with nested includes!

Trust me. I was asked to do this once, on a huge UDF library that had been cobbled together by dozens of different developers over many years. It was an utter nightmare of a job. I had to use 'varscoper', which uncovered over a thousand unscoped variables! I can't imagine what kind of leakage problems they had, before I fixed it!!!! The strange thing is that many of the memory leaks had been plugged by other unscoped variables, which meant that as I fixed one problem, it uncovered a new one somewhere else!

15,880 Comments

@Charles,

Yeah man, those unscoped variable leaks are the worst. It's created some really bad data leaks in my experience. The worst part is that they can be so hard to track down, especially when it "works on my machine" :D

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel