Subject: | C:R:V impostor objects |
I recently got bitten by a behavior of C:R:V objects, that may not be a bug per se, but I believe it should be at least discussed:
It is a reasonably common idiom to test objects in this way:
use Scalar::Util 'blessed';
if ( blessed($thing) && $thing->isa('SomeClass') ) {
# do something with our SomeClass object
...
}
Obviously C:R:V things are always going to "blessed", but when the C:R:V doesn't actually return an object in scalar context we can still
normally get away with the above test since the AUTOLOADed isa will still return nothing:
use Contextual::Return;
my $thing = LAZY { 'some string' };
if ( blessed($thing) && $thing->isa('SomeClass') ) {
# this won't be true because even though our thing is blessed, our isa
# method will return false
}
# we continue on our merry way...
There is a problem with some scalar values though... consider this:
my $thing = LAZY { 123 }; # a numeric scalar
if ( blessed($thing) && $thing->isa('SomeClass') ) {
# this isa method is actually going to die... the real reason error message
# is disguised behind a generic C:R:V message but it actually:
# Can't call method "isa" without a package or object reference
}
This is the "bug" that bit me.. now that I know I might be given C:R:V objects I can work around it, but it was a head scratcher for a while.
And it made me realize that something even more insidious could occur.. consider this:
use DateTime; # for example
# let's say our C:R:V scalar happens to be a string with, unintentionally,
# the same value as the name of a loaded class:
my $thing = LAZY { 'DateTime' };
if ( blessed($thing) && $thing->isa('DateTime') ) {
# this will be true! Oh noes... now we think we have a valid
# DateTime object and anything we try to do with it is probably
# going to fail:
my $date_str = $thing->iso8601;
# FAIL! Can't use string ("DateTime") as a HASH ref while "strict refs"
...
}
I can imagine that if this occurred somewhere, where C:R was not even a consideration, it would be a nightmare to debug!
The same errors would occur using ->can too... which may be worse since I don't believe 'can' is ever supposed to throw an exception.
I feel like that we could "fix" this in C:R.. but I'm not yet entirely convinced that it wouldn't cause other problems.. but here are two
potential ways we might address this:
1.) in C:R:V::AUTOLOAD in the context handler loop, as soon as we have the $object we could do something like:
# after this
my $object = eval { $handler->(@{$attrs->{args}}) };
# do this:
return if ( $requested_method eq 'isa' || $requested_method eq 'can')
&& !blessed $object;
In other words, if we're called with can or isa and our value is not actually an object then just return nothing. This seems fairly safe to me
but perhaps I haven't thought through it entirely.
2.) The other idea was to have C:R monkey patch Scalar::Util::blessed to return false when given a C:R:V that does not resolve to an object,
(and its class when it does; currently blessed will always return C:R:V). Then if someone is not using blessed and gets bit then so be it!
This is not really meant to be a serious solution... I thought it was funny and evil but I suspect would cause waaaaay more problems.
Your thoughts?
(this module is awesome btw... I knew it existed but had never really come across it until it now... )