Tuesday 2 September 2014

Repro of structCopy() bug

G'day:
I could put this in a gist or just post the code directly, but it gives me something to write about today. Be wary of structCopy(). There's a bug been raised about it not working on ColdFusion scopes, which Adobe don't quite seem to be able to understand. The ticket is 3815793.

Firstly: some background. Don't use structCopy(). It's a shitty function, which doesn't do anything useful, coherently. Consider this demonstration:

// demo.cfm

test("Baseline");

test("Original updated", function(){
    original.colours.black = "*****mangu*****";
});

test("Original added to @ top level", function(){
    original.numbers.three = "*****toru*****";
});

test("Original added to @ inner level", function(){
    original.colours.green = "*****kakariki*****";
});


function test(label,process){
    writeOutput("<h3>#label#<h3>");
    makeStructures();    
    isDefined("process") ? process() : false;
    writeDump(var={original=original,ref=ref,copy=copy,dupe=dupe});
    writeOutput("<hr>");
}

function makeStructures(){
    original = {
        colours = {
            black = "pangu"
        }
    };

    ref = original;
    copy = structCopy(original);
    dupe = duplicate(original);
}

Notes:
  • I have a function test()
  • …which creates a test struct…
  • …and performs an operation on it…
  • …dumping out the results.
  • I perform a number of tests:
    • A baseline
    • Changing a value in a sub-struct
    • Adding a value at the top level
    • Adding a value in a sub-struct

And here are the results:

Baseline

struct
COPY
struct
COLOURS
struct
BLACKpangu
DUPE
struct
COLOURS
struct
BLACKpangu
ORIGINAL
struct
COLOURS
struct
BLACKpangu
REF
struct
COLOURS
struct
BLACKpangu

Original updated

struct
COPY
struct
COLOURS
struct
BLACK*****mangu*****
DUPE
struct
COLOURS
struct
BLACKpangu
ORIGINAL
struct
COLOURS
struct
BLACK*****mangu*****
REF
struct
COLOURS
struct
BLACK*****mangu*****

Original added to @ top level

struct
COPY
struct
COLOURS
struct
BLACKpangu
DUPE
struct
COLOURS
struct
BLACKpangu
ORIGINAL
struct
COLOURS
struct
BLACKpangu
NUMBERS
struct
THREE*****toru*****
REF
struct
COLOURS
struct
BLACKpangu
NUMBERS
struct
THREE*****toru*****

Original added to @ inner level

struct
COPY
struct
COLOURS
struct
BLACKpangu
GREEN*****kakariki*****
DUPE
struct
COLOURS
struct
BLACKpangu
ORIGINAL
struct
COLOURS
struct
BLACKpangu
GREEN*****kakariki*****
REF
struct
COLOURS
struct
BLACKpangu
GREEN*****kakariki*****

The reference copy and the duplicate() works as one would expect:

  • the reference copy results in two references pointing to the same struct, so any changes to one is reflected in the other;
  • the duplicate is a completely new struct, so changes to the original have no effect on the duplicate.
However structCopy() does something completely useless: it duplicates the top level of the struct, but what it copies are the references, not the values. So this means if one alters the top level of the original, it's not reflected in the copy; if one alters any of the internal structs, then that is reflected in the copy.

Fence-sitting functionality like this serves no purpose, and simply causes confusion.



But that's not even the problem here. This is documented behaviour. It's how Allaire decided structCopy() ought to work.

The issue is that structCopy() doesn't even copy the top level keys when using it on (some?) ColdFusion scopes.

Repro:

// urlBug.cfm

URL.testKey = "value set in URL scope";
writeDump(var=URL, label="URL scope");

writeOutput('<br><div style="padding-left: 20px;">');
copy = structCopy(URL);
writeDump(var=copy, label="initial state of copy of URL scope");

copy.testKey = "value set in copy";
writeDump(var=copy, label="updated state of copy");
writeOutput('</div><br>');


writeDump(var=URL, label="URL scope after change made to copy");

Output on ColdFusion (on Railo it works correctly):

URL scope - struct
TESTKEYvalue set in URL scope

initial state of copy of URL scope - struct
TESTKEYvalue set in URL scope
updated state of copy - struct
TESTKEYvalue set in copy

URL scope after change made to copy - struct
TESTKEYvalue set in copy

Note how the change to the the value in the URL scope. Let's just compare that to using a struct instead of the URL scope:

// withStruct.cfm

struct.testKey = "value set in struct";
writeDump(var=struct, label="struct");

writeOutput('<br><div style="padding-left: 20px;">');
copy = structCopy(struct);
writeDump(var=copy, label="initial state of copy of struct");

copy.testKey = "value set in copy";
writeDump(var=copy, label="updated state of copy");
writeOutput('</div><br>');

writeDump(var=struct, label="struct after change made to copy");

The only difference there is that i'm using a struct instead of the URL scope. And the output is now as we'd expect:

struct - struct
TESTKEYvalue set in struct

initial state of copy of struct - struct
TESTKEYvalue set in struct
updated state of copy - struct
TESTKEYvalue set in copy

struct after change made to copy - struct
TESTKEYvalue set in struct

Changes to the copy at that top level do not impact the original struct. So that demonstrates the bug, Adobe. I can't help but think the original guidance offered should have been enough:

Steps to Reproduce:
1) do a form post
2) make a structcopy
3) update form key of copy
4) dump / output form
I don't see how that's unclear. Although it does depend on them actually reading what's in front of them, which I am finding to not be one of their strengths.

So I dunno what the chances are of them reading all this, but hey... as the saying goes "I can lead the horse to water (I just can't hold it's head under until it stops struggling)".

--
Adam