File Coverage

blib/lib/Try/Tiny.pm
Criterion Covered Total %
statement 77 81 95.0
branch 30 38 78.9
condition 7 15 46.6
subroutine 12 12 100.0
pod 3 3 100.0
total 129 149 86.5


line stmt bran cond sub pod time code
1             package Try::Tiny; # git description: v0.27-8-g8dc27c7
2 10     10   137593 use 5.006;
  10         25  
3             # ABSTRACT: Minimal try/catch with proper preservation of $@
4              
5             our $VERSION = '0.28';
6              
7 10     10   33 use strict;
  10         12  
  10         182  
8 10     10   30 use warnings;
  10         11  
  10         280  
9              
10 10     10   26 use Exporter 5.57 'import';
  10         120  
  10         457  
11             our @EXPORT = our @EXPORT_OK = qw(try catch finally);
12              
13 10     10   32 use Carp;
  10         8  
  10         2092  
14             $Carp::Internal{+__PACKAGE__}++;
15              
16             BEGIN {
17 10   66 10   63 my $su = $INC{'Sub/Util.pm'} && defined &Sub::Util::set_subname;
18 10   33     37 my $sn = $INC{'Sub/Name.pm'} && eval { Sub::Name->VERSION(0.08) };
19 10 100 66     78 unless ($su || $sn) {
20 9   33     12 $su = eval { require Sub::Util; } && defined &Sub::Util::set_subname;
21 9 50       1926 unless ($su) {
22 0         0 $sn = eval { require Sub::Name; Sub::Name->VERSION(0.08) };
  0         0  
  0         0  
23             }
24             }
25              
26             *_subname = $su ? \&Sub::Util::set_subname
27             : $sn ? \&Sub::Name::subname
28 10 0       41 : sub { $_[1] };
  0 50       0  
29 10 50 33     3968 *_HAS_SUBNAME = ($su || $sn) ? sub(){1} : sub(){0};
30             }
31              
32             my %_finally_guards;
33              
34             # Need to prototype as @ not $$ because of the way Perl evaluates the prototype.
35             # Keeping it at $$ means you only ever get 1 sub because we need to eval in a list
36             # context & not a scalar one
37              
38             sub try (&;@) {
39 54     54 1 3550 my ( $try, @code_refs ) = @_;
40              
41             # we need to save this here, the eval block will be in scalar context due
42             # to $failed
43 54         53 my $wantarray = wantarray;
44              
45             # work around perl bug by explicitly initializing these, due to the likelyhood
46             # this will be used in global destruction (perl rt#119311)
47 54         61 my ( $catch, @finally ) = ();
48              
49             # find labeled blocks in the argument list.
50             # catch and finally tag the blocks by blessing a scalar reference to them.
51 54         88 foreach my $code_ref (@code_refs) {
52              
53 65 100       150 if ( ref($code_ref) eq 'Try::Tiny::Catch' ) {
    100          
54 33 100       160 croak 'A try() may not be followed by multiple catch() blocks'
55             if $catch;
56 32         25 $catch = ${$code_ref};
  32         62  
57             } elsif ( ref($code_ref) eq 'Try::Tiny::Finally' ) {
58 31         21 push @finally, ${$code_ref};
  31         51  
59             } else {
60 1 50       67 croak(
61             'try() encountered an unexpected argument ('
62             . ( defined $code_ref ? $code_ref : 'undef' )
63             . ') - perhaps a missing semi-colon before or'
64             );
65             }
66             }
67              
68             # FIXME consider using local $SIG{__DIE__} to accumulate all errors. It's
69             # not perfect, but we could provide a list of additional errors for
70             # $catch->();
71              
72             # name the blocks if we have Sub::Name installed
73 52         68 my $caller = caller;
74 52         312 _subname("${caller}::try {...} " => $try)
75             if _HAS_SUBNAME;
76              
77             # set up scope guards to invoke the finally blocks at the end.
78             # this should really be a function scope lexical variable instead of
79             # file scope + local but that causes issues with perls < 5.20 due to
80             # perl rt#119311
81             local $_finally_guards{guards} = [
82 52         114 map { Try::Tiny::ScopeGuard->_new($_) }
  31         79  
83             @finally
84             ];
85              
86             # save the value of $@ so we can set $@ back to it in the beginning of the eval
87             # and restore $@ after the eval finishes
88 52         103 my $prev_error = $@;
89              
90 52         46 my ( @ret, $error );
91              
92             # failed will be true if the eval dies, because 1 will not be returned
93             # from the eval body
94 52         64 my $failed = not eval {
95 52         52 $@ = $prev_error;
96              
97             # evaluate the try block in the correct context
98 52 100       106 if ( $wantarray ) {
    100          
99 4         12 @ret = $try->();
100             } elsif ( defined $wantarray ) {
101 7         12 $ret[0] = $try->();
102             } else {
103 41         69 $try->();
104             };
105              
106 23         412908 return 1; # properly set $failed to false
107             };
108              
109             # preserve the current error and reset the original value of $@
110 49         1563 $error = $@;
111 49         59 $@ = $prev_error;
112              
113             # at this point $failed contains a true value if the eval died, even if some
114             # destructor overwrote $@ as the eval was unwinding.
115 49 100       103 if ( $failed ) {
116             # pass $error to the finally blocks
117 26         21 push @$_, $error for @{$_finally_guards{guards}};
  26         105  
118              
119             # if we got an error, invoke the catch block.
120 26 100       62 if ( $catch ) {
121             # This works like given($error), but is backwards compatible and
122             # sets $_ in the dynamic scope for the body of C<$catch>
123 19         23 for ($error) {
124 19         39 return $catch->($error);
125             }
126              
127             # in case when() was used without an explicit return, the C
128             # loop will be aborted and there's no useful return value
129             }
130              
131 8         479 return;
132             } else {
133             # no failure, $@ is back to what it was, everything is fine
134 23 100       433 return $wantarray ? @ret : $ret[0];
135             }
136             }
137              
138             sub catch (&;@) {
139 38     38 1 5857 my ( $block, @rest ) = @_;
140              
141 38 100       374 croak 'Useless bare catch()' unless wantarray;
142              
143 35         60 my $caller = caller;
144 35         248 _subname("${caller}::catch {...} " => $block)
145             if _HAS_SUBNAME;
146             return (
147 35         202 bless(\$block, 'Try::Tiny::Catch'),
148             @rest,
149             );
150             }
151              
152             sub finally (&;@) {
153 38     38 1 3999 my ( $block, @rest ) = @_;
154              
155 38 100       273 croak 'Useless bare finally()' unless wantarray;
156              
157 35         53 my $caller = caller;
158 35         207 _subname("${caller}::finally {...} " => $block)
159             if _HAS_SUBNAME;
160             return (
161 35         161 bless(\$block, 'Try::Tiny::Finally'),
162             @rest,
163             );
164             }
165              
166             {
167             package # hide from PAUSE
168             Try::Tiny::ScopeGuard;
169              
170 10 50   10   44 use constant UNSTABLE_DOLLARAT => ("$]" < '5.013002') ? 1 : 0;
  10         15  
  10         1815  
171              
172             sub _new {
173 31     31   23 shift;
174 31         96 bless [ @_ ];
175             }
176              
177             sub DESTROY {
178 31     31   3040 my ($code, @args) = @{ $_[0] };
  31         98  
179              
180 31         31 local $@ if UNSTABLE_DOLLARAT;
181             eval {
182 31         68 $code->(@args);
183 29         8335 1;
184 31 100       32 } or do {
185 2 50       40 warn
186             "Execution of finally() block $code resulted in an exception, which "
187             . '*CAN NOT BE PROPAGATED* due to fundamental limitations of Perl. '
188             . 'Your program will continue as if this event never took place. '
189             . "Original exception text follows:\n\n"
190             . (defined $@ ? $@ : '$@ left undefined...')
191             . "\n"
192             ;
193             }
194             }
195             }
196              
197             __PACKAGE__
198              
199             __END__