File Coverage

blib/lib/Exception/Died.pm
Criterion Covered Total %
statement 77 80 96.2
branch 24 28 85.7
condition 12 15 80.0
subroutine 11 11 100.0
pod 1 1 100.0
total 125 135 92.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -c
2              
3             package Exception::Died;
4              
5             =head1 NAME
6              
7             Exception::Died - Convert simple die into real exception object
8              
9             =head1 SYNOPSIS
10              
11             use Exception::Died;
12              
13             use warnings FATAL => 'all';
14             eval { open $f, "x", "bad_open_mode" };
15             Exception::Died->throw( message=>"cannot open" ) if $@;
16              
17             eval { die "Bum!\n" };
18             if ($@) {
19             my $e = Exception::Died->catch;
20             $e->throw;
21             };
22              
23             # Can replace die hook globally
24             use Exception::Died '%SIG' => 'die';
25             eval { die "Boom!\n" };
26             print ref $@; # "Exception::Died"
27             print $@->eval_error; # "Boom!"
28              
29             # Can be used in local scope only
30             use Exception::Died;
31             {
32             local $SIG{__DIE__};
33             Exception::Fatal->import('%SIG');
34             eval { die "Boom!" };
35             print ref $@; # "Exception::Died"
36             print $@->eval_error; # "Boom!"
37             };
38             eval { die "Boom" };
39             print ref $@; # ""
40              
41             # Debugging with increased verbosity
42             $ perl -MException::Died=:debug script.pl
43              
44             # Debugging one-liner script
45             $ perl -MException::Died=:debug -ale '\
46             use File::Temp; $tmp = File::Temp->new( DIR => "/notfound" )'
47              
48             =head1 DESCRIPTION
49              
50             This class extends standard L and converts eval's error into
51             real exception object. The eval's error message is stored in I
52             attribute.
53              
54             This class can be also used for debugging scripts with use simple
55             L or L. You can raise verbosity level and print stack
56             trace if script doesn't use L and has stopped with
57             L.
58              
59             =for readme stop
60              
61             =cut
62              
63 2     2   175062 use 5.006;
  2         8  
  2         85  
64              
65 2     2   12 use strict;
  2         4  
  2         73  
66 2     2   9 use warnings;
  2         10  
  2         103  
67              
68             our $VERSION = '0.06';
69              
70 2     2   10 use constant::boolean;
  2         3  
  2         18  
71              
72              
73             =head1 INHERITANCE
74              
75             =over 2
76              
77             =item *
78              
79             extends L
80              
81             =back
82              
83             =cut
84              
85             # Extend Exception::Base class
86             BEGIN {
87              
88             =head1 CONSTANTS
89              
90             =over
91              
92             =item ATTRS : HashRef
93              
94             Declaration of class attributes as reference to hash.
95              
96             See L for details.
97              
98             =back
99              
100             =head1 ATTRIBUTES
101              
102             This class provides new attributes. See L for other
103             descriptions.
104              
105             =over
106              
107             =cut
108              
109 2     2   108 my %ATTRS = ();
110 2         4 my @ATTRS_RO = ();
111              
112             =item eval_error : Str {ro}
113              
114             Contains the message from failed C block. This attribute is
115             automatically filled on object creation.
116              
117             use Exception::Died '%SIG';
118             eval { die "string" };
119             print $@->eval_error; # "string"
120              
121             =cut
122              
123 2         16 push @ATTRS_RO, 'eval_error';
124              
125             =item catch_can_rebless : Str {ro}
126              
127             Contains the flag for C method which marks that this exception
128             object should be reblessed. The flag is marked by internal C<__DIE__>
129             hook.
130              
131             =cut
132              
133 2         3 push @ATTRS_RO, 'catch_can_rebless';
134              
135             =item eval_attribute : Str = "eval_error"
136              
137             Meta-attribute contains the name of the attribute which is filled if
138             error stack is empty. This attribute will contain value of C<$@>
139             variable. This class overrides the default value from
140             L class.
141              
142             =cut
143              
144 2         6 $ATTRS{eval_attribute} = 'eval_error';
145              
146             =item string_attributes : ArrayRef[Str] = ["message", "eval_error"]
147              
148             Meta-attribute contains the format of string representation of exception
149             object. This class overrides the default value from L
150             class.
151              
152             =cut
153              
154 2         23 $ATTRS{string_attributes} = [ 'message', 'eval_error' ];
155              
156             =item default_attribute : Str = "eval_error"
157              
158             Meta-attribute contains the name of the default attribute. This class
159             overrides the default value from L class.
160              
161             =back
162              
163             =cut
164              
165 2         12 $ATTRS{default_attribute} = 'eval_error';
166              
167 2     2   229 use Exception::Base 0.21;
  2         34  
  2         13  
168 2         18 Exception::Base->import(
169             'Exception::Died' => {
170             has => { ro => \@ATTRS_RO },
171             %ATTRS,
172             },
173             '+ignore_package' => [ 'Carp' ],
174             );
175             };
176              
177              
178             ## no critic RequireArgUnpacking
179             ## no critic RequireCarping
180             ## no critic RequireInitializationForLocalVars
181              
182             =head1 IMPORTS
183              
184             =over
185              
186             =item use Exception::Died '%SIG';
187              
188             =item use Exception::Died '%SIG' => 'die';
189              
190             Changes C<$SIG{__DIE__}> hook to C.
191              
192             =item use Exception::Died ':debug';
193              
194             Changes C<$SIG{__DIE__}> hook and sets verbosity level to 4 (maximum).
195              
196             =cut
197              
198             # Handle %SIG tag
199             sub import {
200 16     16   12413 my ($pkg, @args) = @_;
201              
202 16         24 my @params;
203              
204 16         69 while (defined $args[0]) {
205 19         34 my $name = shift @args;
206 19 50       52 if ($name eq ':debug') {
207 0         0 $name = '%SIG';
208 0         0 @args = ('die', 'verbosity', 4, @args);
209             };
210 19 100       44 if ($name eq '%SIG') {
211 9 100 66     50 if (defined $args[0] and $args[0] eq 'die') {
212 5         7 shift @args;
213             }
214             # Handle die hook
215 9         52 $SIG{__DIE__} = \&__DIE__;
216             }
217             else {
218             # Other parameters goes to SUPER::import
219 10         16 push @params, $name;
220 10 100 100     64 push @params, shift @args if defined $args[0] and ref $args[0] eq 'HASH';
221             };
222             };
223              
224 16 100       45 if (@params) {
225 8         47 return $pkg->SUPER::import(@params);
226             };
227              
228 8         53 return TRUE;
229             };
230              
231              
232             =item no Exception::Died '%SIG';
233              
234             Undefines C<$SIG{__DIE__}> hook.
235              
236             =back
237              
238             =cut
239              
240             # Reset %SIG
241             sub unimport {
242 4     4   131 my $pkg = shift;
243 4         11 my $callpkg = caller;
244              
245 4         15 while (my $name = shift @_) {
246 6 100       23 if ($name eq '%SIG') {
247             # Undef die hook
248 4         570 $SIG{__DIE__} = '';
249             };
250             };
251              
252 4         12 return TRUE;
253             };
254              
255              
256             =head1 CONSTRUCTORS
257              
258             =over
259              
260             =item catch(I<>) : Self|$@
261              
262             This method overwrites the default C constructor. It works as method
263             from base class and has one exception in its behavior.
264              
265             my $e = CLASS->catch;
266              
267             If the popped value is an C object and has an attribute
268             C set, this object is reblessed to class I<$class> with its
269             attributes unchanged. It is because original L-EC
270             method doesn't change exception class but it should be changed if
271             C handles C<$SIG{__DIE__}> hook.
272              
273             use Exception::Base
274             'Exception::Fatal' => { isa => 'Exception::Died' },
275             'Exception::Simple' => { isa => 'Exception::Died' };
276             use Exception::Died '%SIG' => 'die';
277              
278             eval { die "Died\n"; };
279             my $e = Exception::Fatal->catch;
280             print ref $e; # "Exception::Fatal"
281              
282             eval { Exception::Simple->throw; };
283             my $e = Exception::Fatal->catch;
284             print ref $e; # "Exception::Simple"
285              
286             =back
287              
288             =cut
289              
290             # Rebless Exception::Died into another exception class
291             sub catch {
292 8     8 1 755 my $self = shift;
293              
294 8 50       20 my $class = ref $self ? ref $self : $self;
295              
296 8         36 my $e = $self->SUPER::catch(@_);
297              
298             # Rebless if called as Exception::DiedDerivedClass->catch()
299 8 100 66     429 if (do { local $@; local $SIG{__DIE__}; eval { $e->isa(__PACKAGE__) } }
  8   100     14  
  8         23  
  8         13  
  8         91  
300             and ref $e ne $class and $e->{catch_can_rebless})
301             {
302 1         6 bless $e => $class;
303             };
304              
305 8         22 return $e;
306             };
307              
308              
309             =head1 METHODS
310              
311             =over
312              
313             =item _collect_system_data(I<>) : Self
314              
315             Collect system data and fill the attributes of exception object. This method
316             is called automatically if exception if thrown. This class overrides the
317             method from L class.
318              
319             See L.
320              
321             =back
322              
323             =cut
324              
325             # Collect system data
326             sub _collect_system_data {
327 56     56   23149 my $self = shift;
328              
329 56 100       132 if (not ref $@) {
    50          
330 50         973 $self->{eval_error} = $@;
331 50         200 while ($self->{eval_error} =~ s/\t\.\.\.propagated at (?!.*\bat\b.*).* line \d+( thread \d+)?\.\n$//s) { }
332 50         92 $self->{eval_error} =~ s/( at (?!.*\bat\b.*).* line \d+( thread \d+)?\.?)?\n$//s;
333 50 100       153 $self->{eval_error} = undef if $self->{eval_error} eq '';
334             }
335 6         9 elsif (do { my $e = $@; local $@; local $SIG{__DIE__}; eval { $e->isa('Exception::Died') } }) {
  6         9  
  6         20  
  6         8  
  6         40  
336 6         18 $self->{eval_error} = $@->{eval_error};
337 6 50 66     47 $self->{eval_error} = undef if defined $self->{eval_error} and $self->{eval_error} eq '';
338             }
339             else {
340 0         0 $self->{eval_error} = undef;
341             };
342              
343 56         205 return $self->SUPER::_collect_system_data(@_);
344             };
345              
346              
347             =head1 FUNCTIONS
348              
349             =over
350              
351             =item __DIE__()
352              
353             This is a hook function for $SIG{__DIE__}. This hook can be enabled with pragma:
354              
355             use Exception::Died '%SIG';
356              
357             or manually, i.e. for local scope:
358              
359             {
360             local $SIG{__DIE__};
361             Exception::Died->import('%SIG');
362             # ...
363             };
364              
365             =back
366              
367             =cut
368              
369             # Die hook
370             sub __DIE__ {
371 25 100   25   9039 if (not ref $_[0]) {
372             # Do not recurse on Exception::Died & Exception::Warning
373 13 100       74 die $_[0] if $_[0] =~ /^Exception::(Died|Warning): /;
374              
375             # Simple die: recover eval error
376 12         40 my $message = $_[0];
377 12         75 while ($message =~ s/\t\.\.\.propagated at (?!.*\bat\b.*).* line \d+( thread \d+)?\.\n$//s) { };
378 12         160 $message =~ s/( at (?!.*\bat\b.*).* line \d+( thread \d+)?\.?)?\n$//s;
379              
380 12         73 my $e = __PACKAGE__->new;
381 12         2734 $e->{eval_error} = $message;
382 12         27 $e->{catch_can_rebless} = TRUE;
383 12         78 die $e;
384             };
385             # Otherwise: throw unchanged exception
386 12         75 die $_[0];
387             };
388              
389              
390             1;
391              
392              
393             =begin umlwiki
394              
395             = Class Diagram =
396              
397             [ <>
398             Exception::Died
399             -----------------------------------------------------------------
400             +catch_can_rebless : Bool {ro}
401             +eval_error : Str {ro}
402             #default_attribute : Str = "eval_error"
403             #eval_attribute : Str = "eval_error"
404             #string_attributes : ArrayRef[Str] = ["message", "eval_error"]
405             -----------------------------------------------------------------
406             <> +catch() : Self|$@
407             #_collect_system_data() : Self
408             <> -__DIE__()
409             <> +ATTRS() : HashRef ]
410              
411             [Exception::Died] ---|> [Exception::Base]
412              
413             =end umlwiki
414              
415             =head1 PERFORMANCE
416              
417             The C module can change C<$SIG{__DIE__}> hook. It
418             costs a speed for simple die operation. The failure scenario was
419             benchmarked with default setting and with changed C<$SIG{__DIE__}> hook.
420              
421             -----------------------------------------------------------------------
422             | Module | Without %SIG | With %SIG |
423             -----------------------------------------------------------------------
424             | eval/die string | 237975/s | 3069/s |
425             -----------------------------------------------------------------------
426             | eval/die object | 124853/s | 90575/s |
427             -----------------------------------------------------------------------
428             | Exception::Base eval/if | 8356/s | 7984/s |
429             -----------------------------------------------------------------------
430             | Exception::Base try/catch | 9218/s | 8891/s |
431             -----------------------------------------------------------------------
432             | Exception::Base eval/if verbosity=1 | 14899/s | 14300/s |
433             -----------------------------------------------------------------------
434             | Exception::Base try/catch verbos.=1 | 18232/s | 16992/s |
435             -----------------------------------------------------------------------
436              
437             It means that C with die hook makes simple die 30 times
438             slower. However it has no significant difference if the exception
439             objects are used.
440              
441             Note that C will slow other exception implementations,
442             like L and L.
443              
444             =head1 SEE ALSO
445              
446             L.
447              
448             =head1 BUGS
449              
450             If you find the bug, please report it.
451              
452             =for readme continue
453              
454             =head1 AUTHOR
455              
456             Piotr Roszatycki
457              
458             =head1 LICENSE
459              
460             Copyright (c) 2008, 2009 by Piotr Roszatycki .
461              
462             This program is free software; you can redistribute it and/or modify it
463             under the same terms as Perl itself.
464              
465             See L