I've had a bit of a break, as you will have noticed. I'm now sitting in my folks' place in Auckland, watching the cricket with me dad. New Zealand are desperately trying to salvage the match against India from certain loss. Currently NZ is on 363/5, with McCullum (183*) and Watling (92*) being the last real line of defence against India. NZ only lead by 118. We kinda need a lead of 250 to not lose (India have another innings yet, and there's still a day and a half to go). We're definitely not gonna win, but we might be able to eke out a draw.
I know hardly any of that will mean anything to most of my readers. However cricket represents "summer" to me.
But enough of the waffle.
Segue alert. One of the most noted wafflers in the CFML community - Scott Stroz - discovered some interesting behaviour when we was migrating some code from ColdFusion to Railo, and initially suspected a bug in Railo. I've had a closer look, and I think it's more likely a bug in ColdFusion, with the behavioural difference with Railo being that it doesn't have the bug. However I'm only 90% convinced of this. Here's the deal...
Scott's ColdFusion code had been using an application-specific mapping to provide pathing information for a
<cfimport>
tag (detailed here: "Re: cfimport with app specific mappings?"). This was not working on Railo, so superficially seemed like a Railo bug, and Scott raised it on the Railo Google Group accordingly. At the same time I was working through this with him via Google Chat (or whatever it is), because I could not even get it to work on ColdFusion, let alone Railo. But I wanted to get it working on CF then perhaps work out how to get it working on Railo. I drew a blank.In working through all this Scott found some interesting behaviour, and I independently arrived at the same finding (had I read what Scott had said properly, he'd actually already mentioned this, but I didn't. Oops). Here's my pared-back repro case anyhow.
// Application.cfc
component {
variables.thisDir = getDirectoryFromPath(getCurrentTemplatePath());
this.name = "importBug01";
this.mappings = {
"/ui" = variables.thisDir & "/lib/tags"
};
}
The only relevant thing here is the mapping. BTW, my file system structure for this application is as follows (this'll make the mapping be easier to follow):
import/
lib/
tags/
strong.cfm
site/
testMapping.cfm
viaImport.cfm
viaInclude.cfm
viaModule.cfm
Application.cfc
The custom tag in question is very simple, and its code is pretty much irrelevant here, but here it is:
// tag.cfm
if (thisTag.executionMode == "end"){
writeOutput("<strong>#trim(thisTag.generatedContent)#</strong>");
thisTag.generatedContent = "";
}
So all this does is to apply
<strong>
tags to the text within the tags.[Watling has just got his century with a boundary: 103* (297 deliveries, 422min). NZ 384/5, lead by 138]
Next we have a baseline test which calls the tag (to demonstrate it works), but without using the mapping:
<!--- viaModule.cfm --->
<cfmodule template = "/ui/strong.cfm">
G'day World
</cfmodule>
And this code, predictably, yields this:
G'day World!
OK, so the tag works fine.
What about this mapping: is that loading in OK?
<!--- testMapping.cfm --->
<cfoutput>#expandPath('/ui')#</cfoutput>
This outputs the correct value, which for me is:
C:\apps\adobe\ColdFusion\10\cfusion\wwwroot\shared\scratch\blogExamples\coldfusion\bugs\import\lib\tags
If I comment out the mapping from Application.cfc, I see the results:
C:\apps\adobe\ColdFusion\10\cfusion\wwwroot\ui
That's just ColdFusion guessing I must mean that
/ui
is a directory off the ColdFusion root.Right. So everything is working correctly / predictably. So far.
Now I try to use that mapping in a
<cfimport>
tag.<!--- viaImport.cfm --->
<cfimport taglib="/ui" prefix="ui">
<ui:strong>G'day World!</ui:strong>
[Brendon McCullum double-century: 203* (395 deliveries. They didn't say how long he's been in there). NZ 399/5, lead by 153]
And now we get something less predictable:
Cannot import the tag library specified by /ui. | |
The following error was encountered: C:/apps/adobe/ColdFusion/10/cfusion/wwwroot/ui. Ensure that you have specified a valid tag library.The CFML compiler was processing:
|
Hmmm. Well we know the directory is fine, as we've accessed it in other adjacent code. And we know the mapping is fine too, as we have used that.
Let's now see what happens if we use a CFAdmin mapping instead:
Active ColdFusion Mappings | |||||||||
|
Result:
G'day World!
Hmmm. Time to RTFM. The docs for
<cfimport>
have this to say:Attributes
Attribute
|
Req/Opt
|
Default
|
Description
|
---|---|---|---|
taglib |
Required
|
Tag library URI. The path must be relative to the web root (and start with /), the current page location, or a directory specified in the Administrator ColdFusion mappings page.
|
But that's not all. This would not be interesting if that was the end of the story.
There's one last file mentioned in that file listing above:
<!--- viaInclude.cfm --->
<cfinclude template="./viaImport.cfm">
All this does is call that
viaImport.cfm
file I mentioned above, via a <cfinclude>. So we should expect the same behaviour as calling viaImport.cfm
directly: an error.Before hitting the file, I remove the mapping from ColdFusion Administrator, restart ColdFusion, and re-hit
viaImport.cfm
, and confirm it is erroring. Yup.Now I browse to
viaInclude.cfm
:G'day World!
Huh??? How is that working? What's "worse" is that now if I browse to
viaImport.cfm
... that works too.And if I restart again, and repeat the process:
- browse to
viaImport.cfm
: error - browse to
viaInclude.cfm
(which simply callsviaImport.cfm
, remember!): works OK - browse to
viaImport.cfm
again: now it works OK.
I went to have a look at what was going on in the compiled files, and this cast some light on the scene. The compiled files after browsing to
viaImport.cfm
are as follows:cfudf2ecfm1426863377.class
cfdetail2ecfm164248976.class
cfexception_en2exml1817986200.class
cfgettemplate2ecfm1834882185.class
cfParseException2ecfm1024591355.class
cfudf2ecfm1426863377$funcENCODEFORERROR.class
cfudf2ecfm1426863377$funcENCODEFORERRORSMART.class
None of those are anything to do with
viaImport.cfm
(or Application.cfc
, which also should be getting compiled), so it looks like things are erroring-out before any code gets actually run. Basically it seems ColdFusion is attempting to compile viaImport.cfm
without first looking up, compiling and executing Application.cfc
. But it needs to run Application.cfc
so as to load the mappings for the /ui
in the <cfimport>
tag to resolve. So viaImport.cfm
can't compile, so we error out. To me this is entirely reasonable. We can't expect runtime considerations like mappings in Application.cfc
to be relevant at the compile time for a file. Even if ColdFusion did compile and run Application.cfc
before attempting to compile viaImport.cfm
.But how, then, is
viaInclude.cfm
working? It shouldn't be. If we clear that lot out, restart, and hit viaInclude.cfm
, we get this lot:cfviaImport2ecfm2014207820.class
cfviaInclude2ecfm1189608697.class
cfApplication2ecfc217786993.class
cfcomponent2ecfc932966791.class
cfstrong2ecfm244369874.class
There we see everything has been compiled as we'd expect (given the code runs). We still don't know why it compiled and ran OK then. How did viaImport.cfm compile unless somehow Application.cfc had compiled and been executed before it was run?
I'm going to do a bit of an experiment here. I've altered
Application.cfc
and viaInclude.cfm
to wait for a minute at key points in their execution:// Application.cfc
component {
variables.thisDir = getDirectoryFromPath(getCurrentTemplatePath());
this.name = "importBug01";
this.mappings = {
"/ui" = variables.thisDir & "/lib/tags"
};
sleep(60*1000);
}
<!--- viaInclude.cfm --->
<cfset sleep(60*1000)>
<cfinclude template="./viaImport.cfm">
My hypothesis is that I'll see
Application.cfc
compiling, 60sec passing, then viaInclude.cfm
compiling, another minute then viaImport.cfm
and strong.cfm
being compiled. Results:cfApplication2ecfc217786993.class (15:54:53)
cfviaInclude2ecfm1189608697.class (15:54:53)
cfviaImport2ecfm2014207820.class (15:56:53)
cfstrong2ecfm244369874.class (15:56:53)
[McCullum 229* and Watling 122* now on a partnership of 350*, off 729 deliveries. NZ 444/5 lead by 198. New ball in one over. Gulp.]
OK, so it's not quite what I expected, but
viaInclude.cfm
and Application.cfc
are definitely compiling and executing before viaImport.cfm
and strong.cfm
are even compiled.[Watling out lbw for 124. NZ 446/6 lead by 200. Watling and McCullum have batted NZ back into this game, but we're definitely more likely to lose than win or even draw at this stage]
And... I've just twigged what's going on.
In
viaInclude.cfm
, we have this code:<cfinclude template="./viaImport.cfm">
Now the path for the template in the
<cfinclude>
is a static value there, but it could be a dynamic value. And the dynamic value could utilise mappings, so ColdFusion has to run through any code that might include a mapping before compiling viaInclude.cfm. Not least of all because it needs to be able to find the file being included.On the other hand, a
<cfimport>
tag cannot take a dynamic value for the path, as evidenced by this code:<!--- viaImport.cfm --->
<cfset importPath = "/ui">
<cfimport taglib="#importPath#" prefix="ui">
<ui:strong>G'day World!</ui:strong>
And this error when I try to run it:
This expression must have a constant value. | |
The CFML compiler was processing:
|
So given that value cannot be dynamic, the ColdFusion compiler doesn't seem to think it might need to resolve mapping values either. Which is... wrong.
There's no reason for compilation to act any differently with a
<cfimport>
as with a <cfinclude>
(or indeed a <cfmodule>
) vis-a-vis the resolution of any mappings that might be in play. It'd make sense if runtime mappings absolutely didn't work; but clearly they do. Just not properly. They will be used when compiling a <cfimport>
tag if they already exist in memory, but the compiler won't go look them up if it doesn't have them. If the compiler can do this for a <cfinclude>
, it should do it with a <cfimport>
as well.In conclusion:
- there's definitely a difference in behaviour here between ColdFusion in Railo;
- ColdFusion has a bug (raised as 3708694) in that it doesn't handle the compilation of
<cfimport>
tags uniformly; - Railo does handle this uniformly, erring towards how ColdFusion documents the behaviour to be;
- ColdFusion should support runtime mappings (ie: those in
Application.cfc
) for<cfimport>
tags, just like it does for<cfinclude>
and<cfmodule>
; - as could/should Railo.
Thoughts?
--
Adam
Oh, and NZ are 492/6. McCullum 251*; Neesham 23*. Lead by 246. 20 overs remaining until stumps on day 4. McCullum and Watling have batted NZ back into this game. Fantastic work. Utterly fantastic.