Showing posts with label Bugs. Show all posts
Showing posts with label Bugs. Show all posts

Saturday 7 May 2022

ColdFusion: probable bug with the implementation of the rest operator

G'day:

ColdFusion 2021 added the spread and rest operators. These are implemented as two different usages of .... In this article I am going to be making an observation about how the implementation of the rest operator is incomplete and faulty. I first raised this in the CFML Slack channel, but I've now wittered on enough about it to copy it to here.

What does the rest operator do? The docs say:

[The] Rest Operator operator is similar to [the] Spread Operator but behaves in [the] opposite way, while Spread syntax expands the iterables into individual element[s], the Rest syntax collects and condenses them into a single element.

ibid.

That's not so useful out of the context of a discussion on the spread operator, that said. So a test should clarify:

function testRest(first, ...rest) {
    return arguments
}

function run() {
    describe("Testing CF's rest operator, eg: function testRest(first, ...rest)", () => {
        it("combines all latter argument values into one parameter value", () => {
            actual = testRest("first", "second", "third", "fourth")
            expect(actual).toBe([
                first = "first",
                rest = ["second", "third", "fourth"]
            ])
            
             writeDump(var=actual, label="actual/expected")
        })
    })
}

tinyTest.runTests()
  • The rest operator is ... as a prefix to the last parameter in a function signature.
  • Any arguments passed from that position on are combined into that one parameter's value.

So far so good. So what's the problem? I had a use case where I needed this to work for named arguments, not positional ones:

it("combines all latter argument values into one parameter value when using named arguments", () => {
    actual = testRest(first="first", two="second", three="third", four="fourth")
    expect(actual.first).toBe("first")
    expect(actual).toHaveKey("rest")
    expect(actual.rest).toBe({two="second", three="third", four="fourth"})
})

Same as before, just the arguments have names now. This test fails. Why? Because CF completely ignores the rest operator when one uses named arguments. This "passing" test shows the actual (wrong) behaviour:

it("doesn't work at all with named arguments", () => {
    actual = testRest(first="first", two="second", three="third", four="fourth")
    expect(actual.first).toBe("first")
    expect(actual?.rest).toBeNull()
    expect(actual.keyList().listSort("TEXTNOCASE")).toBe("FIRST,four,REST,three,two")
    
     writeDump(var=actual, label="actual")
     writeDump(var={first="first", rest={two="second", three="third", four="fourth"}}, label="expected")
})

As I said, I raised this on the CFML Slack channel. I got one useful response:

I think it's unusual to use rest with named parameters, but, CF supports named parameters as well as positional so I would expect it to work. I'd settle for it being fully documented though as only working with positional.

John Whish

Fair point, and as I said in reply:

My reasoning was remarkably similar. I started thinking "it's a bit unorthodox to use named arguments here", but then I stopped to think… why? And my conclusion was "because in other languages I use this operation there's no such thing as named arguments, so I've not been used to thinking about it", and that was the only reason I could come up with for me to think that. So I binned that thought (other than deciding to ask about it here).

The thing is there's no good reason I can think of that named arguments should not work. One cannot mix named and positional arguments which would be one wrinkle, so it's 100% reliable to take a set of named arguments, and match the argument names to the parameter names in the method signature. There is no ambiguity: any args that have the same name as a param are assigned as that parameter value. All the rest - instead of being passed in as ad-hoc arguments - are handled by the ... operation.

I cannot see a way that there's any ambiguity either. It's 100% "match the named ones, pass all others in the rest param".

What happens if the method call actually specifies a named argument that matched the name of the "rest" param? Same as if one specifies a positional argument in the position of the "rest" param: it doesn't matter. all arguments that don't match other named params are passed in the rest argument value.

I also think that if for some reason named arguments are not supported for use on function using the rest operator, then an exception should be thrown; not simply the code being ignored.

And whatever the behaviour is needs to be documented.

However one spins it, there are at least two bugs here:

  • Either it should work as I'd expect (or some variation thereof, if I have not thought of something), or it should throw an exception.
  • The behaviour should be documented.

I have not raised tickets for these as I'm not really a user of CF any more, so I don't care so much. Enough to raise it with them; not enough to care what they do about it. But CFers probably should care.

NB: I did not realise Lucee did not support the spread and rest operators at all, so I had to take a different approach to my requirement anyhow. I've not decided on the best way as yet.

There is a ticket for them to be implemented in Lucee: LDEV-2201.

The tests I wrote for this can be run on trycf.com.

Righto.

--
Adam

Sunday 17 April 2022

A day in the life of trying to write a blog article in the CFML ecosystem

G'day:

This is not the article I intended to write today. That article was gonna be titled "CFML: Adding a LogBox logger to a CFWheels app via dependency injection", but I'll need to get to that another day now.

Here's how far that article got before the wheels fell off:

And that was it.

Why? Well I started by writing an integration test just to check that box install logbox did what I expected:

import test.BaseSpec
import logbox.system.logging.LogBox

component extends=BaseSpec {

    function run() {
        describe("Tests the LogBox install", () => {
            it("is instantiable", () => {
                expect(() => getComponentMetadata("LogBox")).notToThrow()
            })
        })
    }
}

Simple enough. It'll throws an exception if LogBox ain't there, and I'm expecting that. It's a dumb test but it's a reasonable first step to build on.

I run the test:

Err… come again? I ain't installed it yet. I lifted the code from the expect callback out and run it "raw" in the body ofthe test case: predictable exception. I put it back in the callback. Test passes. I change the matcher to be toThrow. Test still passed. So this code both throws and exception and doesn't throw an exception. This is pleasingly Schrödingeresque, but not helpful.

The weird thing is I know this is not a bug in TestBox, cos we use notToThrow in our tests at work. I port the test over to my work codebase: test fails (remember: this is what I want ATM, we're still at the "red" of "red-green-refactor").

I noticed that we were running a slightly different version of Testbox in the work codebase: 4.4.0-snapshot compared to my 4.5.0+5. Maybe there's been a regression. I changed my TestBox version in box.json and - without thinking things through - went box install again (not just box install testbox which is all I really needed to do), and was greeted with this:

That's reasonably bemusing as I had just used box install fw1 to install it in the first place, and that went fine. And I have not touched it since. I checked what version I already had installed (in framework/box.json), and it claims 4.3.0. So… ForgeBox… I beg to differ pal. You found this version y/day, why can't you find it today? I check on ForgeBox, and for 4.x I see versions 4.0.0, 4.1.0, 4.2.0, 4.5.0-SNAPSHOT. OK, so granted: no 4.3.0. Except that's what it installed for me yesterday. Maybe 4.3.0 has issues and got taken down in the last 24h (doubtful, but hey), so I blow away my /framework directory, and remove the entry from box.json, and box install fw1 again. This is interesting:

root@280d80cf28c6:/var/www# box install fw1
√ | Installing package [forgebox:fw1]
|------------------------------------------------
| Verifying package 'fw1' in forgebox, please wait...
| Installing version [4.2.0].

4.2.0. But its entry in its own box.json is 4.3.0, and the constraint it put in my box.json is ^4.3.0.

I do not have time or inclination for any of this, so I just stick a constraint of ~4.2.0 in my box.json, and that seems to have solved it. I mean the error went away: it's still installing 4.3.0. Even with a hard-coded 4.2.0 it's still installing 4.3.0.

Brad Wood from Ortus/CommandBox had a look at this, nutted-out that there was something wrong with the way the FW/1 package on ForgeBox was configured, and he in turn pinged Steve Neiland who looks after FW/1 these days, and he got this sorted. I'm now on 4.3.0, and it says it's 4.2.0. And box install no longer complains at me. Cheers fellas.

Then I noticed that because of the stupid way CFWheels "organises" itself in the file system, I have inadvertantly overwritten a bunch of my own CFWheels files. Sigh. CFWheels doesn't bother to package itself up as "app" (its stuff) and "implementation" (my code that uses their app), it just has "here's some files: some you should change (anything outside the wheels subdirectory), some you probably shouldn't (the stuff in the wheels subdirectory)", but there's no differentiation when it comes to installation: all the files are deployed. Overwriting all the user-space files with their original defaults. Sorry but this is just dumbarsey. Hurrah for source control and small commit iterations is all I can say, as I could just revert some files and I was all good.

Right so now I have the same version of TestBox installed here as in our app at work (remember how this was all I was tring to do? Update testbox. Nothing to do with FW/1, and nothing to do with CFWheels. But there's an hour gone cocking around with that lot).

And the test still doesn't work. Ballocks.

I notice the Lucee version is also different. We're locked into an older version of Lucee at work due to bugs and incompats in newer versions that we're still waiting on to be fixed, so the work app is running 5.3.7.47, and I am on 5.3.8.206. Surely it's not that? I rolled my app's Lucee version back to 5.3.7.47 and the test started failing (correctly). OK, so it's a Lucee issue.

I spent about an hour messing around doing a binary search of Lucee container versions until I identified the last version that wasn't broken (5.3.8.3) and the next version - a big jump here - 5.3.8.42 that was broken. I looked at a diff of the code but nothing leapt out at me. This was slightly daft as I had no idea what I was looking for, so that was probably half an hour of time looking at Lucee's source code in an aimless fashion. I actually saw the change that was the problem, but didn't clock that that is what caused it at the time.

Having drawn a blank, I slapped my forehead, called myself a dick, and went back to the code in TestBox that was behaving differently. That would obviously tell me where to look for the issue.

I tracked the problem down to here, in system/Assertion.cfc:

    // Message+Detail regex must not match
    if (
        len( arguments.regex ) AND
        (
            !arrayLen( reMatchNoCase( arguments.regex, e.message ) ) OR !arrayLen(
                reMatchNoCase( arguments.regex, e.detail )
            )
        )
    ) {
        return this;
    }

    fail( arguments.message );
}

I distilled that down into a portable repro case:

s = ""
pattern = ".*"
writeDump([
    reMatch = s.reMatchNoCase(pattern),
    matches = s.matches(pattern),
    split = s.split(pattern)
])

There are some Java method calls there to act as controls, but on Lucee's current version, we get this:

And on earlier versions it's this:

(Full disclosure: I'm using Lucee 4.5 on trycf.com for that second dump, but it's the same results in earlier versions of Lucee 5, up to the point where it starts going wrong)

Note how previously a regex match of .* matches an empty string? This is correct. It does. In all regex engines I know of. Yet in Lucee's current versions, it returns a completely empty array. This indicates no match, and it's wrong. Simple as that. So there's the bug.

I was pointed in the direction of an existing issue for this: LDEV-3703. Depsite being a regression they know they caused, Lucee have decided to only fix it in 6.x. Not the version they actually broke. Less than ideal, but so be it.

There were a coupla of Regex issues dealt with between those Lucee versions I mentioned before. Here's a Jira search for "fixversion >= 5.3.8.4 and fixversion < 5.3.8.42 and text ~ regex". I couldn't be arsed tracking back through the code, but I did find something in LDEV-3009 mentioning a new Application.cfc setting:

this.useJavaAsRegexEngine = true

This is documented for ColdFusion in Application variables, and… absolutely frickin' nowhere in the Lucee docs, as far as I can see.

On a whim I stuck that setting in my Application.cfc and re-ran the test. If the setting was false: the test doesn't work. If it was true: the test does work. That's something, but Lucee is not off the hook here. The behaviour of that regex match does not change between the old and new regex engines! .* always matches an empty string! So there's still a bug.

However, being pragmatic, I figured "problem solved" (for now), and moved on. For some reason I restarted my container, and re-hit my tests:

I switched the setting to this.useJavaAsRegexEngine = false and the tests ran again (failed incorrectly, but ran). So… let me get this straight. For TestBox to work, I need to set that setting to true. To get CFWheels to work, I need to set it to false.

For pete's sake.

As I said on the Lucee subchannel on the CFML Slack:

Do ppl recall how I've always said these stupid flags to change behaviour of the language were a fuckin dumb idea, and are basically unusable in a day and age where we all rely on third-party libs to do our jobs?

Exhibit. Fucking. A.

Every single one of these stupid, toxic, setting doubles the overhead for library providers to make their code work. I do not fault TestBox or CFWheels one bit here. They can't be expected to support the exponential number of variations each one of those settings accumulates. I can firmly say that no CFML code should ever be written that depends on any of these settings. And no library or third-party code should ever be tested with the setting variation on. Just ignore them. The settings should not exist. Anyway: this is an editorial digression. "We are where we are" as the over-used saying goes.


Screw all this. Seriously. All I wanted to do is to do a blog article about perhaps 50-odd new lines of code in my example app. Instead I spent four hours untangling this shite. And my blog article has not progressed.

Here's what I needed to do to my app to work around these various issues:

This is all committed to GitHub as 0.5.1

Writing this sure was cathartic. I think I was my own audience for this one. Ah well. Good on you if you got to here.

Righto.

--
Adam

Wednesday 9 June 2021

Repro for code that breaks in ColdFusion but works in Lucee

G'day:

In the last article (CFML: messing around with mixins (part 2)) I used some code that worked in Lucee, but didn't work in ColdFusion. It was not my code's fault, it's ColdFusion's fault for not being able to parse it. I didn't think I could be arsed looking at it this evening, but someone's asked me about it already so I had a look. I can reproduce the issue, and work around it.

Here's the code wot works on Lucee but breaks on ColdFusion (on trycf.com):

obj = {
    myMethod = function () {return arguments}
}    
methodName = "myMethod"
    
useProxy = true

callMe = useProxy
    ? () => obj[methodName](argumentCollection=arguments)
    : obj[methodName]

result = callMe("someArg")
writeDump(result)

This errors with: Invalid CFML construct found on line 10 at column 8. on line 10

It's pointing to the arrow function shorthand, but if I change it back to a normal function expression using the function keyword, it still errors.

It's also not the dynamic method call either. In the past that would have caused problems on ColdFusion, but they've fixed it at some point.

It's also not the lack of semi-colons ;-)

This adjustment works (on trycf.com):

obj = {
    myMethod = function () {return arguments}
}
methodName = "myMethod"
    
useProxy = true

proxy = () => obj[methodName](argumentCollection=arguments)
callMe = useProxy
    ? proxy
    : obj[methodName]

result = callMe("someArg")
writeDump(result)

It seems like ColdFusion really didn't like that arrow function / function expression in the ?: expression.

I'd say this is definitely a bug, and will point this repro case to Adobe for them to… do whatever they like with it.

Righto

--
Adam

Tuesday 18 May 2021

I play the game "how long will it take me to find a new bug in ColdFusion" again, after a few years

G'day:

Answer: about two minutes to guess what would likely be broken, and I think I had an initial repro created in another minute after that.

Last time I tried this it took 44min: "ColdFusion bug challenge: how quickly can one find a bug in ColdFusion 11?".

This time I figured "ah what's a language feature in CF2021? I reckon anything more than superficial use of one of those will break. Um… the rest operator. I bet it can't handle type-checking properly". Actually that didn't even take two minutes to type, but it was about how long I thought about it. So: given I was actually right about that guess, it took me - what - 20seconds? I'm not claiming to be smart or anything here. It's just so frickin easy to find things wrong with CFML that that is as long as it might take.

For context, here's the rest operator in action in a simple situation:

function untypedVariadicArguments(string s, ...x) {
    return arguments
}
result = untypedVariadicArguments("some string", 1, "two", ["three"])

writeDump(result)

The rest operator enables one to define variadic functions: ones that can take any number of arguments. Of course CFML already supported this: you can pass whatever you like to a UDF / method, and all the values will be there in the arguments scope, whether you specify them in the function signature or not. The difference is that in a variadic function, all the trailing arguments are put in the same one parameter value as an array. The output of above is:

 


 That is working properly.

The bug comes in when I try to type-check the that x parameter, eg: it can have any number of x values but they all need to be numbers. I would expect the syntax to be like this:

function typedVariadicArguments(string s, numeric ...x) {
    return arguments
}

In most curly-brace languages I can find that do type-checked variadic functions, the type declaration is done before the parameter declaration (irrespective of whether the parameter syntax is ...paramName or paramName..., as that can vary). See the examples on that page on Wikipedia I link to above on variadic functions. If I run that code in ColdFusion though, I get this:

Invalid CFML construct found on line 2 at column 52. on line 2

(Ha, by the by I also just found a wee bug in Lucee. Lucee does not support the rest operation, and when I accidentally tried to run this on Lucee cos I was not paying attention to what I was doing on trycf.com, it errored with "invalid argument definition on line 2": it's a parameter not an argument. The parameter is the definition. The argument is the value you pass)

So I went "OK well there's a good chance they just did it wrong with ColdFusion", so I tried it like this:

function typedVariadicArguments(string s, ...numeric x) {
    return arguments
}

Now that at least compiled, however it seems to be completely ignoring the rest operator. I'm testing it with the same test code as above:

result = typedVariadicArguments("some string", 1, "two", ["three"])

writeDump(result)

This is how one would expect it to work with just a signature like this:

function typedVariadicArguments(string s, numeric x) {
    return arguments
}

ColdFusion really ought not be going "buggered if I know what those dots are doing there: I'll just ignore them". Note that is understanding that as the rest operator. If you try two dots or four dots, you'll get a compile error.

I'm going with two bugs here, actually:

  • An implementation omission in that numeric ...x should work. That's the syntax they should have used, and it's not valid to not implement the type check there.
  • ColdFusion should not be ignoring th rest operator in a method signature if it doesn't know what to do with it. It should error.
  • OK a third bug I think. It'll allow the rest operator on the non-last parameter without erroring (untypedVariadicArguments(...s, x)): it doesn't work, but again it just ignores it.

I'm gonna stop looking now.

Am I missing something? There are all bugs right? I'm not getting something wrong?

Righto.

--
Adam

Thursday 6 May 2021

Lucee: what now for goodness sake

G'day:

I'm just writing this here because it's too long to put in a message in the CFML/Lucee Slack channel, and so I can get some eyes on it.

Consider this code (test.cfm):

<cfoutput>
<cfset relativeFilePath = "../getCanonicalPathIssue/targetfile.cfm">
Relative file path: [#relativeFilePath#]<br>
Does relative path exist: [#fileExists(relativeFilePath)#]<br>
<br>

<cfset expandedPath = expandPath(relativeFilePath)>
Expanded file path: [#expandedPath#]<br>
Does expanded path exist: [#fileExists(expandedPath)#]<br>
<br>

<cfset canonicalPathFromRelativeFilePath = getCanonicalPath(relativeFilePath)>
Canonical path from relative path: [#canonicalPathFromRelativeFilePath#]<br>
Does canonical pathh from relative path exist: [#fileExists(canonicalPathFromRelativeFilePath)#]<br>
<br>

<cfset canonicalPathFromExpandedPath = getCanonicalPath(expandedPath)>
Canonical path from expanded path: [#canonicalPathFromExpandedPath#]<br>
Does canonical path from expanded path exist: [#fileExists(canonicalPathFromExpandedPath)#]<br>
<br>

<cfset directory = getDirectoryFromPath(canonicalPathFromRelativeFilePath)>
Directory: #directory#<br>
Directory contents and do they exist:<br>
#directoryList(directory).reduce((buffer="", filePath) => buffer & "#filePath#: #fileExists(filePath)#<br>")#
</cfoutput>

And this is its output:

Relative file path: [../getCanonicalPathIssue/targetfile.cfm]
Does relative path exist: [false]

Expanded file path: [/var/www/public/nonWheelsTests/getCanonicalPathIssue/targetfile.cfm]
Does expanded path exist: [true]

Canonical path from relative path: [/var/www/public/nonWheelsTests/getCanonicalPathIssue/targetFile.cfm]
Does canonical path from relative path exist: [false]

Canonical path from expanded path: [/var/www/public/nonWheelsTests/getCanonicalPathIssue/targetfile.cfm]
Does canonical path from expanded path exist: [true]

Directory: /var/www/public/nonWheelsTests/getCanonicalPathIssue/
Directory contents and do they exist:
/var/www/public/nonWheelsTests/getCanonicalPathIssue/test.cfm: true
/var/www/public/nonWheelsTests/getCanonicalPathIssue/targetfile.cfm: true

Basically I've got a second file (targetfile.cfm) in the same directory as I'm running this code from (test.cfm), and I'm giving Lucee a (valid) relative path to it, and asking some questions. Everything goes OK until I get to the result of getCanonicalPath(relativeFilePath): it's upper-cased part of the file name!??! Also note that expandPath(relativeFilePath) gets it right. And that getCanonicalPath(expandedPath) also gets it right.

The last bit of the code just shows what's def in the directory concerned.

I actually know what's causing the problem. A coupla hours ago I renamed the file from targetFile.cfm to targetfile.cfm. note the change in capitalisation, and how the old version matches the bad value getCanonicalPath is coming up with. So Lucee is caching that for some reason. I also found where to uncache it, but am buggered if I know why this setting caches file paths:

That's supposed to be about how often the contents of the file are checked; nothing about file paths. But if I set it to "Always (Bad)", then the problem goes away. It's clearly this lesser used definition of "bad", that means "actually works".

So that's an hour or so of my life I'll never get back. Thanks.


For shits and giggles I decided to run this on ColdFusion:

I'd give ColdFusion a pass here for choking on a relative path if not for two things:

  • it's specifically documented as dealing with them: "Absolute or relative path of a directory or to a file."
  • The path is a valid relative path. There's no trickery with the casing going on here, or caching or anything like that. The relative path I'm providing is the path to that file, relative to where the path is being used.

Oh well. I suppose I'll check with ppl to see if there's a reason why this behaviour on each platform, isn't wrong in the way I claim it is… and then raise some bugs. I'll cross ref once I've done that, but it'll be tomorrow now.

Righto.

--
Adam

Tuesday 4 May 2021

abort! abort;!

G'day

What do you (CFMLers, sorry) make of this?

<cfscript>
function f(){
    writeOutput("in f<br>")
    abort
}

writeOutput("before f<br>")
f()
writeOutput("after f<br>")
</cfscript>

What would you expect that to output? The foolhardly money would go on:

before f
in f

Because, like, it's obvious: after f is never called because we're aborting in the function. Right?

Well yer partly right. On ColdFusion, that's exacly what happens (I tested on CF2021). On Lucee, however, I get this:

before f
in f
after f

Um… kewl.

The thing is that if one adds a semi-colon after the abort, Lucee starts behaving.

<cfscript>
function f(){
    writeOutput("in f<br>")
    abort;
}

writeOutput("before f<br>")
f()
writeOutput("after f<br>")
</cfscript>

I had some abort-confusion the other day on the CFML / Lucee Slack channel because this had behaviour I did not expect:

But that's actually correct behaviour. abort takes an optional string parameter showError, and writeOutput returns true (don't ask), which can be coerced into a string. If a statement has only one parameter, its name can be omitted, so Lucee is interpretting that as a multi-line abort statement - with a showError value of true - that doesn't end until the explicit semi-colon.

This is not the same though, as far as I can tell. The } of the function block is an explicit end-of-statement token just like a semi-colon is. Or at least it ought to be.


In other news, this sample code won't run correctly on trycf.com, which you can check out at https://trycf.com/gist/759318ea257c7ca7130c1f12c3ee72f8: Lucee doesn't work as expected whether or not the semi-colon is there; and CF only works as expected when the semi-colon is not there. This is at odds to how it works on (my) actual servers. I'd be interested in what behaviour you get on your servers?

Am still wondering a bit if I'm missing something here; especially given the different varieties of behaviour…

Righto.

--
Adam

Friday 24 March 2017

Help explain closure to the Adobe ColdFusion Team

G'day:
OK, this is a CFML-centric article. But it's also a call to provide code analogies in different languages to a CFML example, to show the ColdFusion Team they dunno what they're on about.

Here's some CFML code:

function doit() {
    var a = ["a", "b", "c"];
    var b = ["x", "y", "z"];
    var counter = 0;
    
    arrayEach(a, function(foo) {
        counter = 0;
        arrayEach(b, function(bar) {
            counter++;
            // in dump counter is always 0
            writeDump({
                counter: counter, 
                foo: foo, 
                bar: bar
            });
        });
    });
}
doit();

This leverages closure to reference the outer counter variable within the innermost function expression. Ergo, it's the same variable. So the expected output of this would be:


Note how the counter is declared in the main function, reset in the outer arrayEach handlers and incremented for each iteration of the inner arrayEach call. So it'll cycle through 1,2,3 three times when output.

That was run on Lucee. Running it on ColdFusion yields:


See how the counter is messed up: it's always zero. It should increment for each iteration of the inner function expression.

Adobe - being their typical selves - is claiming this is "by design":

https://tracker.adobe.com/#/view/CF-4197194:


That's nonsense.

Other languages behave predictably:

JavaScript:

function doit() {
    var a = ["a", "b", "c"];
    var b = ["x", "y", "z"];
    var counter = 0;
    
    a.forEach(function(foo) {
        counter = 0;
        b.forEach(function(bar) {
            counter++;
            // in dump counter is always 0
            console.log({
                counter: counter, 
                foo: foo, 
                bar: bar
            });
        });
    });
}
doit();
VM128:11 Object {counter: 1, foo: "a", bar: "x"}
VM128:11 Object {counter: 2, foo: "a", bar: "y"}
VM128:11 Object {counter: 3, foo: "a", bar: "z"}
VM128:11 Object {counter: 1, foo: "b", bar: "x"}
VM128:11 Object {counter: 2, foo: "b", bar: "y"}
VM128:11 Object {counter: 3, foo: "b", bar: "z"}
VM128:11 Object {counter: 1, foo: "c", bar: "x"}
VM128:11 Object {counter: 2, foo: "c", bar: "y"}
VM128:11 Object {counter: 3, foo: "c", bar: "z"}


And PHP (apologies for the rubbish way PHP does closure):

function doit() {
    $a = ["a", "b", "c"];
    $b = ["x", "y", "z"];
    $counter = 0;
    
    array_walk($a, function($foo) use (&$counter, $b) {
        $counter = 0;
        array_walk($b, function($bar) use (&$counter, $foo) {
            $counter++;
            // in dump counter is always 0
            var_dump([
                "counter" => $counter, 
                "foo" => $foo, 
                "bar" => $bar
            ]);
        });
    });
}
doit();


array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "a"
  ["bar"]=>
  string(1) "z"
}
array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "b"
  ["bar"]=>
  string(1) "z"
}
array(3) {
  ["counter"]=>
  int(1)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "x"
}
array(3) {
  ["counter"]=>
  int(2)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "y"
}
array(3) {
  ["counter"]=>
  int(3)
  ["foo"]=>
  string(1) "c"
  ["bar"]=>
  string(1) "z"
}


But I'm quite keen to know if there's any language that behaves as ColdFusion does in this example? If you've got a mo', could you knock out an equivalent of this code in [your language of choice], and share the results?

I'm 99.999% sure Adobe are just not quite getting closure, but wanna make sure I'm not missing anything.

Righto.

--
Adam

Friday 19 February 2016

ColdFusion 2016: critical regression in scoping

G'day:
Thanks to Mingo Hagen for inspiring me today. He brought this to my attention on the CFML Slack channel (all the more reason to subscribe to that, btw... to catch stuff like this).

ColdFusion 2016 has introduced a serious regression bug when it comes to unscoped references in functions.

Update (2016-07-23)

See Wil's comment below. He's confirmed this is fixed in ColdFusion 2016, Update 2. Cheers for letting us know about this, Wil.
Here's the repro:

function foo(bar) {
    bar = "test";
    try {
        writeOutput("arguments scope");
        writeDump(var=[arguments.bar]);
    } catch (any e){
        writeDump({message=e.message});
    }
    try {
        writeOutput("local scope");
        writeDump(var=[local.bar]);
    } catch (any e){
        writeDump({message=e.message});
    }
    try {
        writeOutput("no scope");
        writeDump(var=[bar]);
    } catch (any e){
        writeDump({message=e.message});
    }
    try {
        writeOutput("variables scope");
        writeDump(var=[variables.bar]);
    } catch (any e){
        writeDump({message=e.message});
    }
}

writeOutput("Test with no arg passed:<br>");
foo();

writeOutput("<hr>Test with arg value passed:<br>");
foo("arg value");

The conceit here is that we are assigning an un-scoped variable a value within the function.

On previous versions of ColdFusion (and Lucee/Railo) we get this:



So CF successfully notices that the unscoped bar is a reference to bar argument, and assigns it appropriately. And the behaviour is uniform irrespective of whether we pass a value for the bar argument or not. This is correct.

On ColdFusion 2016, we get this instead:





Here ColdFusion isn't bothering to do a scope look-up to see if the unscoped variable already exists in a scope, it just goes "unscoped = variables scope". Which, in a function, is wrong.

This is a pretty serious bug. All the more reason to give ColdFusion 2016 a swerve at least for the time being (if not permanently, because it's just not worth the hassle given the lack of any real gain one would get from "upgrading" to it).

Mingo's not raised a ticket for this yet, but I'll cross-ref once he has. It's 4119653.

Righto.

--
Adam

Thursday 19 November 2015

... and... Adobe hot fixes it

G'day:
Just a quick follow-up to this morning's article: "CAUTION: Latest ColdFusion 11 patch breaks (at least some) code using "/>" in CFML tags"

Well knock me over with a feather... they're released a hot-fix for it! How good is that?

It's attached to the ticket: "CFPOP doesn't create the query given by name="" with updater 7 installed".

I still think they need to withdraw update 7, bake that hot fix into it, and release it again (as update 8), but at least this should get Tom going.

I haven't tested it as I'm behind a firewall here and cannot get to a POP server, but I'll report back when I have confirmation it actually fixes the problem.

Good snappy work there Adobe. And good to see it's possible for you to release individual hot fixes.

Righto.

--
Adam

CAUTION: Latest ColdFusion 11 patch breaks (at least some) code using "/>" in CFML tags

G'day:
I twittered about this yesterday, but didn't have time to follow it up here for one reason or other.

I think one should hold off on installing ColdFusion 11 update 7 for the time being, as it definitely has a minor code-breaking glitch, but it could be more far-reaching. This still needs to be clarified by the Adobe ColdFusion Team.

Yesterday Tom Chiverton drew my attention to this new issue: CFPOP doesn't create the query given by name="" with updater 7 installed, wherein:

Pre-updater 7, and as documented, CFPOP's name argument can be used to name the query returned.

Post-updater 7, this errors with " Variable MAIL is undefined. <br>The error occurred on line 10. "
Line 10 being the CFDUMP. Trying to use it in a QoQ also fails.

Test Configuration

<cfpop server="xxx"
action="getHeaderOnly"
username="xxx"
password="xxx"
timeout="60"
name="mail"
maxRows="100"
startRow="5"
/>
<cfdump var="#mail#" />

Seems inconvenient, and I think <cfpop> is a common enough tag that it needs an immediate fix. Bear inind that Update 7 is a security update, so all things being equal, ColdFusion 11 users really ought to have it in production as soon as they can.

Adobe then fed-back on the issue (props for feeding back quickly, btw), but the news ain't cool:

The work-around for this issue is to remove the "/" end tag "/" in cfpop tag, it would work.

Modified the code :

<cfpop server="xxx"
action="getHeaderOnly"
username="xxx"
password="xxx"
name="mail">

<cfdump var="#mail#" />


There is already a bug logged for this bug #3969304(though the description of the bug and the behavior is different. These are the side effects of having end tag), which is fixed and would be available in the next update.

My first reaction here is that "the next update" - unless otherwise stated - could be a coupla months off: the gap between update 6 and update 7 was 2.5 months or so. This is unacceptable. This bug was clearly introduced in update 7 - Adobe are not even contesting that - so they need to fix update 7. We can't be waiting around for 2-3 months until update 8 comes out.

Adobe, you need to own situations wherein you cause your clients grief.

Secondly... and this didn't occur to me initially... if closing the <cfpop /> tag causes it to not work... how many other tags are impacted by this? I don't see how the closed / not closed behaviour change can be individual-tag-specific. This needs clarification from Adobe.

[Actually "secondly" was a sense of epicaricary that people who unnecessarily close their CFML tags, further cluttering up already cluttered code have had their chickens come home to roost. But that is counterproductive in this situation].

Thirdly. This just demonstrates that Adobe need to rethink their security patching, and their approach to patching in general. A security patch should only impact the security issue. It should not be a roll-up of all previous patches, because it's too big a regression burden for the urgency of these security holes they keep finding in the sieve that is ColdFusion. Adobe need to change their work focus away from what's convenient for them, to be what's convenient for their clients. They also need to bear in mind that all of this, irrespective of the patch size and complexity is a) their fault; b) causing their clients work. This is another thing the Adobe ColdFusion Team need to start owning. They need to start acting like they're the stewards of enterprise grade software, basically.

So I think there's some question marks over ColdFusion 11 update 7 at the moment, and the ColdFusion Team need to get a hustle on to release update 8 as soon as they can.

I will be hitting the #CFML Slack channel to try to get this followed up ASAP, so we can get some visibility on all this.

Righto.

--
Adam

Tuesday 29 September 2015

ColdFusion bugs: credit where it's due

G'day:
I never thought I'd have cause to write something like this:

But it's true. The bulk of the bug tickets I've raised have been fixed. Many more are flagged to be fixed. There's a bunch that have been "deferred" or "closed/nuh-uh/nothappening", but they're mostly inconsequential.

So I myself have nothing to add to this drive to get the ColdFusion Team to look at outstanding bugs that are important to us (earlier: "A real prospect of getting engagement from the ColdFusion Team regarding your "favourite" bugs").

I guess I've been impacted by bugs other people have raised, so I'll sift through those too.


Weird.

--
Adam

A real prospect of getting engagement from the ColdFusion Team regarding your "favourite" bugs

G'day:
Dan Fredericks, backed by Elishia Dvorak and later Denard Springle have asked me to spread the word and garner some interest in an idea they've had to improve engagement between the ColdFusion community and the Adobe ColdFusion Team. We had a round of emails yesterday, and Dan and Denard have come up with this write-up:

This past weekend while attending NCDevCon 2015, Adobe... at the bequest and in conjunction with CFML community members... came up with an idea that we hope will help foster and facilitate better communication between the Adobe CF development team and community members.

The idea, in a nutshell, is to take advantage of the momentum we are building as a community with our Slack channel and work together to generate a list of the communities most pressing and important CF bugs that need addressed. They want us to use the Slack channel [#adobe] to accomplish this.

To participate, simply join the channel and work as a community to come up with a list of the most important bugs.

There are some caveats to this request... We ask for you to put in the bug number from the Adobe bug base and give a concise use case for why it should be in this list. Other community members can then comment on the bug and/or the use case. This way we can have open community dialog on the bugs to make sure we get the best and more pressing list of bugs for Adobe.

[The] channel will be used to do this for 3 weeks[...]. All community comments [in this period] will be used to aggregate the bug numbers and business cases, and turned over to the CF team to have a larger internal discussion of those bugs.

After a timely review of the aggregated data by the Adobe CF team they will update us on the status of the bugs on the list via the [#adobe] channel [you need to have subscribed to the #cfml channel first before that will work], a blog post and/or other Slack channels.

This status will include which bugs were accepted to be addressed, and which ones were not accepted. In each case the community expects a fuller explanation of what is chosen to be addressed and what is not, and why those decisions were made. It is our hope that this may lead to a more inclusive discussion of the bugs that are elected not to be addressed if the reasons given turn out to be poor. It is also the community’s expectation that the bug base will likewise be updated with fuller explanations for the actions taken on these bugs.

We hope by using Slack, which is getting a lot of daily use, the community can come together more readily with the Adobe CF development team and put together the best list possible. If this is a successful venture, then we will try using Slack more aggressively with the Adobe CF dev team to foster a more open relationship that benefits all concerned parties.

Wednesday 23 September 2015

New bug status for ColdFusion bugs: DELETED/NEVERHAPPENED

G'day:
This is just a repro of something Aaron said on the bugbase, which reveals a rather interesting act of weirdness on the part of the Adobe ColdFusion Team. And you know how I love those.


I spotted this today on the Adobe bug tracker (look quick, before it gets redacted!):

[ANeff] Bug for: Adobe is deleting thousands of tickets from their public bug tracker

Adobe is deleting thousands of tickets from their public bug tracker. 2,824 ColdFusion tickets have been deleted between June 9, 2015 and September 22, 2015.

Steps to Reproduce:
1) Try to view any of these ColdFusion tickets which were fixed in CF11 Update 3: 3041747, 3043855
2) Try to view any of these ColdFusion tickets which were fixed in CF11 Update 5: 3037144, 3039708, 3041684, 3041790, 3043111, 3043375, 3043657, 3044064, 3114274, 3226380, 3369472, 3520983, 3673298, 3842326
3) View attached 20150609_CFTicketsTotal.jpg and see total ColdFusion ticket count was 5,140 on June 9, 2015
4) View attached 20150922_CFTicketsTotal.jpg and see total ColdFusion ticket count was 2,316 on September 22, 2015
5) View attached 20150609_CFTicketsFiled.jpg and see -my- total ColdFusion ticket count was 455 on June 9, 2015
6) View attached 20150922_CFTicketsFiled.jpg and see -my- total ColdFusion ticket count was 328 on September 22, 2015

Action Items:
1) Restore the tickets deleted between June 9, 2015 and now.
2) Delete no more tickets.



My emphasis.

I can confirm a lot of the bugs I have raised have been deleted. I don't track the numbers, but I can see they're way down. Also looking at one of my older blog articles ("Most popular unhandled ColdFusion bugs"), all the ones I checked from that list - and a reminder: those are the most popular unhandled bugs: so ones we clearly care about - have been deleted.

So what's going on here then? What on earth would possess Adobe to delete this stuff? Even if a ticket has been fixed, it's all valuable historical information... not everyone will be patched-up or running the current version, and the ColdFusion Team has a nasty habit of closing tickets when they can't be arsed doing anything about it, so a lot of historical bugs will still actually be potentially impacting their clients. All this sort of action is achieving is increasing potential work for their clients as they troubleshoot issues which might actually already have been identified and discussed (if not fixed).

What's more, the content of those tickets is - on the whole - the community's work, not Adobe's. I put effort into tickets I raise so they're googleable and reproduceable and are intended to stay there so as to save people time if they run across the same issue. So please don't delete my bloody work, Adobe. Or Aaron's work. Or Ray's or anyone else's.

And, um... please explain.

Update:

And, indeed, they have explained. Apparently it was not their team that did it, it was another team. The team who looks after the Adobe bug tracker were a bit too zealous in making older versions of ColdFusion unselectable when raising new tickets, and in the process made all the tickets for those versions "invisible". Okey dokey then. I do have to wonder why no-one on the ColdFusion Team noticed this change, or checked the results of it, or... seemed to care, prior to Aaron bringing it up..?

--
Adam

Monday 25 May 2015

Some CFML code that doesn't work

G'day:
I was sitting at Lord's yesterday watching England v NZ (and, um, we'll have no comments about that, thank-you very much), and a sudden thought popped into my head "Adam Presley might've been onto something there... if I leverage that....I wonder if I could get that code down to one statement?"

And that of course will mean nothing to anyone.

Lucee seems to mess up boolean expressions (kinda)

G'day:
Another bloody stumbling block writing this code (see CFML / Lucee: beware of "optional" semi-colons for the first one). This time Lucee's not getting the result of a OR expression right. Seriously. Well: I guess not as right as I'd like it to.

CFML / Lucee: beware of "optional" semi-colons

G'day:
Not what I had in mind writing up today, but as often is the way... the easiest way to find bugs in CFML is it try to use it.

Sunday 11 January 2015

CFML: design brain-fart in Application-specific DSN definitions on ColdFusion

G'day:
This article is just some analysis (and opinion, unsurprisingly) on the situation described in this bug ticket: "THIS.datasources changes ignored until CF restart".

In ColdFusion 11 (and Railo 4) one can specify data source definitions in Application.cfc, for example:

// Application.cfc
component {
    this.name = "myApp";

    this.datasources = {
        myDsn    = {
            database    = "dbName",
            host        = "localhost",
            port        = "3306",
            driver        = "MySQL5",
            username    = "dbUser",
            password    = "dbPassword"
        }
    };
    this.datasource    = "myDsn";
}

(That's for ColdFusion 11. Railo's syntax differs slightly: there's an example of it further down).

Wednesday 17 December 2014

Hands up: who has issues accessing the ColdFusion bugbase?

G'day:
There is a continuing issue with access to the Adobe ColdFusion bugbase: when clicking on a link (like this one: https://bugbase.adobe.com/index.cfm?event=bug&id=3909712), many many people get one of two things:

  1. redirected to a login page (that URL requires no login, so that is invalid);
  2. put into a perpetual redirect loop between ticket and login page.

I think other people have different symptoms too. And a select few actually get to see the bug. 75% of the time, I cannot get to the bug page unless I use an incognito page. Sometimes if I clear my adobe.com cookies I can get in without an incognito window, but often I still can't.

Adobe are looking at this (our man Kapil is the star there), but they cannot replicate it. This gobsmacks me, TBH, as so many people seem to have issues.

Do me a favour will you? Go click on that bug URL above, and post back what you get? Do you go straight to the ticket (lucky bugger), do you have to login? Do you get into a redirect loop? Something else? If you could let me know your OS, browser (/version) etc, that might help. If you can't get in initially, but blow awway your Adobe cookies, does that help? Anything relevant, really. If you're happy to liaise with Kapil directly, please let him know on Twitter: @KaroraKapil.

Cheers for your help.

--
Adam

PS: oh and vote for that bug, will you? Cheers.

Friday 28 November 2014

Local scope bug (or not, if you're the ColdFusion Team)

G'day:
I just spotted this, and decided to look into it:

This defines a bugsituationbug in ColdFusion wherein if one deletes a local-scoped variable from a function, then assign an unscoped variable, that new variable goes back in the local scope, not in the variables scope.