Sunday, 26 April 2015

CFML: and, for that matter: not everything that looks like a struct actually is a struct

G'day:
Coincidentally very similar issue (to "CFML: just cos something looks like an array doesn't mean it is an array") came up on Friday.

I'll not bang on about it too much, but an Exception object is not a struct, despite appearances. Here's some repro code:
// exception.cfm
sampleStruct = {key="value"};
writeDump(sampleStruct);

try {
    1/0;
} catch (any e){
    writeDump(e);
    writeOutput("<hr>");
    runSafe("structKeyExists()", function(){
        writeOutput("structKeyExists(): " & structKeyExists(e, "type"));
    });
    runSafe("keyExists()", function(){
        writeOutput(".keyExists(): " & e.keyExists("type"));
    });
    runSafe("isStruct()", function(){
        writeOutput("isStruct(): " & isStruct(e));
    });
    runSafe("param", function(){
        param struct e;
        writeOutput("param: true");
    });
    runSafe("requiresStruct()", function(){
        writeOutput("requiresStruct(): " & requiresStruct(e));
    });
    runSafe("returnsStruct()", function(){
        writeOutput("returnsStruct(): " & isObject(returnsStruct(e)));
    });
    runSafe("duplicate()", function(){
        writeOutput("duplicate(): " & isStruct(duplicate(e)));
    });
}

function requiresStruct(required struct struct){
    return true;
}

struct function returnsStruct(required any struct){
    return struct;
}

And its output:


So an Exception is mostly not a struct, and doesn't work like one. However structKeyExists() does work, and duplicate()-ing an Exception actually returns a struct, not a duplicate of what was passed in. The latter is definitely a bug. duplicate() should either return a duplicate of what it's passed, or it should error if it's not able to do that. It should not return something that's kind of a cheap Adobe knock-off of the original object.

Lucee is weirder still. Check this out:


The trapped exception seems to disappear... oh... I know what's happening (that wasn't for effect: I literally twigged as I started to type that sentence). It looks like there's a scoping/context issue with the e from the outer exception colliding with the e from the inner one in my runsafe() function (I omitted it from the code dump above as it's the same as I always use):

function runSafe(message, task){
    writeOutput("<h3>#message#</h3>");
    try {
        task();
    } catch (any e){
        writeOutput("#e.type# #e.message# #e.detail#");
    } finally {
        writeOutput('<hr>');
    }
}

If I change the exception name used in runsafe() to something other than e, what do we get:



Hmmm. There's a coupla glitches there. If Lucee's gonna maintain that an Exception is a struct, then struct methods need to work on it, and keyExists() doesn't (I presume others don't too). Also my test for returnsStruct() checks if the returned value is an object (just to get a true to display for the ColdFusion version, to make sure it was still an Exception). However it seems Lucee thinks an Exception object - whilst being a struct - is not an object. Humph. So a few Lucee bugs here too.

Anyway, enough of this nonsense. Just bear in mind that an Exception object is not a struct. Other than when it is. Sigh.

--
Adam

Bugs raised:

  • ColdFusion duplicate() bug: 3976478;
  • ColdFusion keyExists() bug: 3976479;
  • Lucee exception variable weirdness: LDEV-321 - I have subsequently realised this is only an issue on Lucee 5; it's fine on Lucee 4.5;
  • Lucee keyExists() bug: LDEV-322 - this has been fixed in 4.5.1.016;
  • Lucee not considering an Exception an object: LDEV-323.