Subject: | Involuntary stringification of $@ in previous_exception |
My exception class overloads "", and I noticed that its stringification function was being called unexpectedly. The following code sets the stage:
package E1 {
use Carp 'cluck';
use Moo;
with 'Throwable';
use overload '""' => sub { cluck ("stringify ", $_[0]->text) };
has text => ( is => 'ro' );
}
eval {
# set up $@
eval { E1->throw( text => 1 ) };
# this will cause the default code for previous_exception to
# access $Throwable::_HORRIBLE_HACK{ERROR}, which will trigger
# stringification
E1->throw( text => 2 );
};
And here is the output:
stringify 1 at throw line 8.
E1::__ANON__('E1=HASH(0xe4f9c8)', undef, '') called at (eval 13) line 34
E1::new(undef, 'text', 2) called at /home/dj/.perlbrew/libs/perl-5.16.2@dev/lib/perl5/Throwable.pm line 38
Throwable::throw('E1', 'text', 2) called at throw line 16
eval {...} called at throw line 14
$ perl throw
stringify 1 at throw line 8.
E1::__ANON__('E1=HASH(0x2397a08)', undef, '') called at (eval 13) line 34
E1::new(undef, 'text', 2) called at /home/dj/.perlbrew/libs/perl-5.16.2@dev/lib/perl5/Throwable.pm line 38
Throwable::throw('E1', 'text', 2) called at throw line 20
eval {...} called at throw line 13
With appropriate instrumentation one finds that the stringification occurs at line 18 in Throwable.pm:
15 has 'previous_exception' => (
16 is => 'ro',
17 default => Sub::Quote::quote_sub(q<
18 if ($Throwable::_HORRIBLE_HACK{ERROR}) {
19 $Throwable::_HORRIBLE_HACK{ERROR}
20 } elsif (defined $@ and (ref $@ or length $@)) {
21 $@;
22 } else {
23 undef;
24 }
25 >),
26 );
Replacing the conditional with something more like that in line 20:
15 has 'previous_exception' => (
16 is => 'ro',
17 default => Sub::Quote::quote_sub(q<
18 my $e;
19 if (defined( $e = $Throwable::_HORRIBLE_HACK{ERROR})
20 && ( ref $e || $e ) ) {
21 $e;
22 } elsif (defined $@ and (ref $@ or length $@)) {
23 $@;
24 } else {
25 undef;
26 }
27 >),
28 );
should(!?) replicate the logic of the original conditional without triggering stringification.
(Well, it passes the tests and it doesn't cause my test code to cluck).
The copy to $e might appear to be a bit less efficient, but benchmarking between
that and using $Throwable::_HORRIBLE_HACK{ERROR} directly didn't seem to make a difference. (I benchmarked the above code; had $@ been some longish string and not a reference, perhaps that might actually make using $Throwable::_HORRIBLE_HACK{ERROR} directly more efficient.)
Patch attached.
Thanks,
Diab
Subject: | Throwable-0.200008.patch |
# This is a patch for Throwable-0.200008.orig to update it to Throwable-0.200008
#
# To apply this patch:
# STEP 1: Chdir to the source directory.
# STEP 2: Run the 'applypatch' program with this patch file as input.
#
# If you do not have 'applypatch', it is part of the 'makepatch' package
# that you can fetch from the Comprehensive Perl Archive Network:
# http://www.perl.com/CPAN/authors/Johan_Vromans/makepatch-x.y.tar.gz
# In the above URL, 'x' should be 2 or higher.
#
# To apply this patch without the use of 'applypatch':
# STEP 1: Chdir to the source directory.
# STEP 2: Run the 'patch' program with this file as input.
#
#### End of Preamble ####
#### Patch data follows ####
diff -c 'Throwable-0.200008.orig/lib/Throwable.pm' 'Throwable-0.200008/lib/Throwable.pm'
Index: ./lib/Throwable.pm
*** ./lib/Throwable.pm Tue Apr 30 12:50:22 2013
--- ./lib/Throwable.pm Wed May 29 13:01:22 2013
***************
*** 15,22 ****
has 'previous_exception' => (
is => 'ro',
default => Sub::Quote::quote_sub(q<
! if ($Throwable::_HORRIBLE_HACK{ERROR}) {
! $Throwable::_HORRIBLE_HACK{ERROR}
} elsif (defined $@ and (ref $@ or length $@)) {
$@;
} else {
--- 15,24 ----
has 'previous_exception' => (
is => 'ro',
default => Sub::Quote::quote_sub(q<
! my $e;
! if (defined( $e = $Throwable::_HORRIBLE_HACK{ERROR})
! && ( ref $e || $e ) ) {
! $e;
} elsif (defined $@ and (ref $@ or length $@)) {
$@;
} else {
#### End of Patch data ####
#### ApplyPatch data follows ####
# Data version : 1.0
# Date generated : Wed May 29 13:06:47 2013
# Generated by : makepatch 2.05
# Recurse directories : Yes
# Excluded files : (\A|/).*\~\Z
# (\A|/).*\.a\Z
# (\A|/).*\.bak\Z
# (\A|/).*\.BAK\Z
# (\A|/).*\.elc\Z
# (\A|/).*\.exe\Z
# (\A|/).*\.gz\Z
# (\A|/).*\.ln\Z
# (\A|/).*\.o\Z
# (\A|/).*\.obj\Z
# (\A|/).*\.olb\Z
# (\A|/).*\.old\Z
# (\A|/).*\.orig\Z
# (\A|/).*\.rej\Z
# (\A|/).*\.so\Z
# (\A|/).*\.Z\Z
# (\A|/)\.del\-.*\Z
# (\A|/)\.make\.state\Z
# (\A|/)\.nse_depinfo\Z
# (\A|/)core\Z
# (\A|/)tags\Z
# (\A|/)TAGS\Z
# p 'lib/Throwable.pm' 2212 1369846882 0100644
#### End of ApplyPatch data ####
#### End of Patch kit [created: Wed May 29 13:06:47 2013] ####
#### Patch checksum: 59 2078 60654 ####
#### Checksum: 77 2780 53097 ####