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: https://lucee.daemonite.io/t/optimizing-your-code-scope-cascading/428
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
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:
https://lucee.daemonite.io/t/local-scope-clarification-re-cfsavecontent/7632/2