More scope questions

I’m seeing a lot of CFLint complaints about code like this:


<cffunction name="convertPDFtoImage">
  <cfargument name="PDFFullDirectoryPath" type="string" required="true">
  <cfargument name="imageType" type="string" default="png">
  ...
  
  <cfset var filename = "#getFileFromPath(PDFFullDirectoryPath)#">
  <!--- 
    Variable PDFFullDirectoryPath should not be referenced in local and argument scope.
    cflint(ARG_VAR_MIXED)
  --->
  ...

  <cfif NOT (imageType EQ "jpg" OR imageType EQ "png")>
    <!--- 
      Variable imageType should not be referenced in local and argument scope.
      cflint(ARG_VAR_MIXED)
    --->
    <cfset imageType = "png">
    <!---
      Variable imageType should not be declared in both local and argument scopes.
      cflint(ARG_VAR_CONFLICT)
    --->
  </cfif>
  ...
</cffunction>

According to the cflint docs, ARG_VAR_MIXED is “info”, but ARG_VAR_CONFLICT is classified as an “error”.

I read the scope cascading blog post: Optimizing Your Code - Scope Cascading

So, what is Lucee actually doing with the code here?

In the first case (unscoped PDFFullDirectoryPath), My understanding is that it would follow the scope chain and actually use arguments.PDFFullDirectoryPath. Same for the second ARG_VAR_MIXED case.

In the third statement, ARG_VAR_CONFLICT suggests that, rather than assigning to arguments.imageType, it’s creating and assigning to a new local variable…either local.imageType, or variables.imageType, depending on the server’s localMode setting (false, in this case, but cflint doesn’t know that)

So is this actually an error, or is it just a case of CFLint being super picky? Is it ok to refer to function arguments unscoped? Is it ok to reassign their values?

If I explicitly refer to arguments.PDFFullDirectoryPath and arguments.imageType in all of the above statements, the cflint complaints go away, which suggests that the code might be working as expected when unscoped, but I’m not sure, especially in the last case.

If I assign to unscoped imageType, would it actually create a new locally scoped variable instead of modifying the existing one in arguments? It’s a weird thought…in most languages, function arguments are local function variables that can be reassigned with no issues, but if cflint is to be believed, the situation might be such that, if I called the function with imageType="foo", then within the function, after validating imageType, I would have arguments.imageType == "foo" and variables.imageType == 'png'.

But the blog post doesn’t actually show where arguments is in the scope hierarchy. So after the assignment when I refer to unscoped imageType, is it variables.imageType or arguments.imageType? Judging by cflint, I’m guessing that the cascade order goes either [local, variables, arguments, ...] or [local, arguments, variables, ...]

I’m thinking that the best idea is probably to explicitly scope the variable references (or assign arguments to local variables), and I’ll be doing that in my new code… but there’s a lot of old code that I’d like to be able to quickly scan though and fix glaring problems before I get around to actually rewriting anything. And I’ll need to really understand how it works to keep from copying earlier bugs :stuck_out_tongue:

Oh… just realized that CFLint is parsing the variable reference inside a template string in this case:

"#getFileFromPath(PDFFullDirectoryPath)#"

…so much for my earlier theory:

Aw geez…

Now I’m even more confused. There more scopes than I realized…and the list doesn’t even show local.

OK, I’m gonna assume that local is the highest possible precedence? above all of these?

And precedence between the different scope types…do they all go in that order? e.g.

[
  local, 
  arguments, 
  ..., 
  variables, 
  ..., 
  form, 
  ..., 
  url, 
  ..., 
  application, 
  ..., 
  session, 
  ...
]

yeah, local comes before arguments…

that way you can do var myArg in a function which doesn’t resolve to arguments.myArg if also defined, when you reference in myArg… somewhat quirky

honestly, if you set in Application.cfc

this.localMode = "modern";
this.scopeCascading = "small";

you can avoid most scoping troubles which cflint flags/misses

Thanks!
I was aware of localMode before, but hadn’t heard of the scopeCascading setting.

I’ll need to do a lot more testing…I’m not aware of any code that uses cgi or cookie scopes implicitly , but so far the only thing that obviously broke was my logbox config (which, ironically, is brand new code).

The docs say that the config component should look like this:

function configure () {
  logBox = { 
    appenders =  { ... },
    ...
  }
}

…which always looked fishy to me…like, shouldn’t it return something? or…I dunno…something?
Anyways, it immediately broke with localMode on, so I tried a few things, like

var logBox = {...};
return logBox;

Which did nothing, I looked at the logbox config code where it was breaking and tried

this.logBox = {...}; 

which didn’t work either. Finally I realized that localMode is basically automatically assigning new variables to local scope rather than variables, so

variables.logBox = {...};

did the trick.

…so this brings up another subtlety which has been bothering me:

What exactly is the difference between this and variables? From what I understand, both are a way of attaching variables to the current cfc instance, right? Are they basically equivalent, but separate scopes? Is using this vs variables in your components just a style issue? (e.g. application.cfc’s config settings are in this but LogBox’s are in variables)
…or are there situations where there is actually a reason to use one over the other?

What happens if you name a variable the same as a scope?
e.g. something else that turns up in the code a lot:

<cfargument name="url" type="string">

CFLint doesn’t seem to mind, which, in itself, is weird, considering all of the opinions it seems to have about “proper” variable naming conventions. As far as I can tell, Lucee doesn’t mind either…the code seems to run OK in this case…but what if I wanted to access url.some_url_param? Does arguments.url just clobber the entire url scope for the duration of this function?

Hehe, turns out there are literally thousands of unscoped references to cgi, session and client variables spread around… oh well.

@Zackster It may have gotten lost in my ramblings, but I would really like to know your thoughts on this:

Thanks for all your help!

This covers it quite well

1 Like