Ryley Ryley - 3 months ago 8
Perl Question

die with reference to an object that overloads bool

I'm using Data::FormValidator to deal with some data validation in DBIx::Class (via DBIx::Class::Validation). DBIC::Validation ultimately does

croak $results
if the validation fails, where
$results
is a Data::FormValidator::Results object. Unfortunately, that croak does not trigger my try/catch around the DBIC calls.

Digging around a bit, I made this simplified test case (excluding DBIC entirely):

use strict;
use Data::FormValidator;
use TryCatch; #or Try::Tiny or eval, same results for each

#setup a profile and values that fail under that profile
my $input_profile = {
required => [ qw( good_ip bad_ip ) ],
constraints => {
good_ip => 'ip_address',
bad_ip => 'ip_address',
}
};

my $validator = new Data::FormValidator({default => $input_profile});

my $input_hashref = {
'good_ip' => '127.0.0.1',
'bad_ip' => '300.23.1.1',
};
try {
my $results = $validator->check($input_hashref,'default');
die $results;
} catch (Data::FormValidator::Results $e) {
print STDERR "failed with ".scalar(@{$e->invalid('bad_ip')})." invalid\n";
}


I would expect that my catch block would get triggered. Instead, nothing happens (execution continues).

Looking at the source of the Results object, I see that it overloads
bool
with it's
success
method. Removing that fixes my issue, but I don't understand why. If that's the whole problem, is there a good way to work around it?

Answer

TL;DR

This is a bug in TryCatch. $results stringifies to the empty string and TryCatch calls if $@ when it should call if defined $@.


Here's an example without Data::FormValidator:

use strict;
use warnings 'all';
use 5.010;

package Foo;

use overload '""' => sub { '' };

sub new {
    bless {}, $_[0];
}

package main;

use TryCatch;

try {
    my $foo = Foo->new;
    die $foo;
}
catch($e) {
    say "<<<$e>>>";
}

TryCatch uses Devel::Declare to inject custom code when the Perl lexer encounters certain keywords. In this case, it generates something like this:*

try;
{
    local $@;
    eval {
        my $foo = Foo->new;
        die $foo;
    };
    $TryCatch::Error = $@;
}

if ($TryCatch::Error) {

Since $@ is the empty string, if ($TryCatch::Error) is false and the catch block is never entered.

This is a bug (one of many for TryCatch). Use eval or Try::Tiny instead (just remember to check for defined, not truthy/falsey).


* If you want to see exactly what gets injected, set the environment variable TRYCATCH_DEBUG to 1.