File Coverage

blib/lib/Error.pm
Criterion Covered Total %
statement 201 249 80.7
branch 59 108 54.6
condition 20 34 58.8
subroutine 34 39 87.1
pod 17 17 100.0
total 331 447 74.0


line stmt bran cond sub pod time code
1             # Error.pm
2             #
3             # Copyright (c) 1997-8 Graham Barr . All rights reserved.
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6             #
7             # Based on my original Error.pm, and Exceptions.pm by Peter Seibel
8             # and adapted by Jesse Glick .
9             #
10             # but modified ***significantly***
11              
12             package Error;
13             $Error::VERSION = '0.17030';
14 26     26   2198945 use strict;
  26         41  
  26         877  
15 26     26   108 use warnings;
  26         51  
  26         2128  
16              
17             use overload (
18             '""' => 'stringify',
19             '0+' => 'value',
20 34     34   99 'bool' => sub { return 1; },
21 26         229 'fallback' => 1
22 26     26   16974 );
  26         37925  
23              
24             $Error::Depth = 0; # Depth to pass to caller()
25             $Error::Debug = 0; # Generate verbose stack traces
26             @Error::STACK = (); # Clause stack for try
27             $Error::THROWN = undef; # last error thrown, a workaround until die $ref works
28              
29             my $LAST; # Last error created
30             my %ERROR; # Last error associated with package
31              
32             sub _throw_Error_Simple
33             {
34 12     12   21 my $args = shift;
35 12         46 return Error::Simple->new( $args->{'text'} );
36             }
37              
38             $Error::ObjectifyCallback = \&_throw_Error_Simple;
39              
40             # Exported subs are defined in Error::subs
41              
42 26     26   5083 use Scalar::Util ();
  26         68  
  26         47469  
43              
44             sub import
45             {
46 29     29   11336042 shift;
47 29         123 my @tags = @_;
48 29         107 local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
49              
50             @tags = grep {
51 29 100       65 if ( $_ eq ':warndie' )
  26         113  
52             {
53 14         70 Error::WarnDie->import();
54 14         42 0;
55             }
56             else
57             {
58 12         54 1;
59             }
60             } @tags;
61              
62 29         32532 Error::subs->import(@tags);
63             }
64              
65             # I really want to use last for the name of this method, but it is a keyword
66             # which prevent the syntax last Error
67              
68             sub prior
69             {
70 1     1 1 1 shift; # ignore
71              
72 1 50       4 return $LAST unless @_;
73              
74 0         0 my $pkg = shift;
75 0 0       0 return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
    0          
76             unless ref($pkg);
77              
78 0         0 my $obj = $pkg;
79 0         0 my $err = undef;
80 0 0       0 if ( $obj->isa('HASH') )
    0          
81             {
82             $err = $obj->{'__Error__'}
83 0 0       0 if exists $obj->{'__Error__'};
84             }
85             elsif ( $obj->isa('GLOB') )
86             {
87 0         0 $err = ${*$obj}{'__Error__'}
88 0 0       0 if exists ${*$obj}{'__Error__'};
  0         0  
89             }
90              
91 0         0 $err;
92             }
93              
94             sub flush
95             {
96 0     0 1 0 shift; #ignore
97              
98 0 0       0 unless (@_)
99             {
100 0         0 $LAST = undef;
101 0         0 return;
102             }
103              
104 0         0 my $pkg = shift;
105 0 0       0 return unless ref($pkg);
106              
107 0 0       0 undef $ERROR{$pkg} if defined $ERROR{$pkg};
108             }
109              
110             # Return as much information as possible about where the error
111             # happened. The -stacktrace element only exists if $Error::DEBUG
112             # was set when the error was created
113              
114             sub stacktrace
115             {
116 1     1 1 2 my $self = shift;
117              
118             return $self->{'-stacktrace'}
119 1 50       20 if exists $self->{'-stacktrace'};
120              
121 0 0       0 my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
122              
123 0 0       0 $text .= sprintf( " at %s line %d.\n", $self->file, $self->line )
124             unless ( $text =~ /\n$/s );
125              
126 0         0 $text;
127             }
128              
129             sub associate
130             {
131 0     0 1 0 my $err = shift;
132 0         0 my $obj = shift;
133              
134 0 0       0 return unless ref($obj);
135              
136 0 0       0 if ( $obj->isa('HASH') )
    0          
137             {
138 0         0 $obj->{'__Error__'} = $err;
139             }
140             elsif ( $obj->isa('GLOB') )
141             {
142 0         0 ${*$obj}{'__Error__'} = $err;
  0         0  
143             }
144 0         0 $obj = ref($obj);
145 0         0 $ERROR{ ref($obj) } = $err;
146              
147 0         0 return;
148             }
149              
150             sub new
151             {
152 25     25 1 60 my $self = shift;
153 25         216 my ( $pkg, $file, $line ) = caller($Error::Depth);
154              
155 25         232 my $err = bless {
156             '-package' => $pkg,
157             '-file' => $file,
158             '-line' => $line,
159             @_
160             }, $self;
161              
162             $err->associate( $err->{'-object'} )
163 25 50       633 if ( exists $err->{'-object'} );
164              
165             # To always create a stacktrace would be very inefficient, so
166             # we only do it if $Error::Debug is set
167              
168 25 100       115 if ($Error::Debug)
169             {
170 1         105 require Carp;
171 1         28 local $Carp::CarpLevel = $Error::Depth;
172 1 50       29 my $text = defined( $err->{'-text'} ) ? $err->{'-text'} : "Error";
173 1         851 my $trace = Carp::longmess($text);
174              
175             # Remove try calls from the trace
176 1         32 $trace =~
177             s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
178 1         11 $trace =~
179             s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
180 1         13 $err->{'-stacktrace'} = $trace;
181             }
182              
183 25         166 $@ = $LAST = $ERROR{$pkg} = $err;
184             }
185              
186             # Throw an error. this contains some very gory code.
187              
188             sub throw
189             {
190 23     23 1 2573620 my $self = shift;
191 23         120 local $Error::Depth = $Error::Depth + 1;
192              
193             # if we are not rethrow-ing then create the object to throw
194 23 100       181 $self = $self->new(@_) unless ref($self);
195              
196 23         309 die $Error::THROWN = $self;
197             }
198              
199             # syntactic sugar for
200             #
201             # die with Error( ... );
202              
203             sub with
204             {
205 0     0 1 0 my $self = shift;
206 0         0 local $Error::Depth = $Error::Depth + 1;
207              
208 0         0 $self->new(@_);
209             }
210              
211             # syntactic sugar for
212             #
213             # record Error( ... ) and return;
214              
215             sub record
216             {
217 0     0 1 0 my $self = shift;
218 0         0 local $Error::Depth = $Error::Depth + 1;
219              
220 0         0 $self->new(@_);
221             }
222              
223             # catch clause for
224             #
225             # try { ... } catch CLASS with { ... }
226              
227             sub catch
228             {
229 16     16 1 62 my $pkg = shift;
230 16         29 my $code = shift;
231 16   100     112 my $clauses = shift || {};
232 16   100     76 my $catch = $clauses->{'catch'} ||= [];
233              
234 16         44 unshift @$catch, $pkg, $code;
235              
236 16         96 $clauses;
237             }
238              
239             # Object query methods
240              
241             sub object
242             {
243 0     0 1 0 my $self = shift;
244 0 0       0 exists $self->{'-object'} ? $self->{'-object'} : undef;
245             }
246              
247             sub file
248             {
249 11     11 1 15 my $self = shift;
250 11 50       96 exists $self->{'-file'} ? $self->{'-file'} : undef;
251             }
252              
253             sub line
254             {
255 11     11 1 16 my $self = shift;
256 11 50       98 exists $self->{'-line'} ? $self->{'-line'} : undef;
257             }
258              
259             sub text
260             {
261 1     1 1 2 my $self = shift;
262 1 50       21 exists $self->{'-text'} ? $self->{'-text'} : undef;
263             }
264              
265             # overload methods
266              
267             sub stringify
268             {
269 12     12 1 20 my $self = shift;
270 12 50       40 defined $self->{'-text'} ? $self->{'-text'} : "Died";
271             }
272              
273             sub value
274             {
275 3     3 1 20 my $self = shift;
276 3 50       14 exists $self->{'-value'} ? $self->{'-value'} : undef;
277             }
278              
279             package Error::Simple;
280             $Error::Simple::VERSION = '0.17030';
281             @Error::Simple::ISA = qw(Error);
282              
283             sub new
284             {
285 22     22 1 39 my $self = shift;
286 22         48 my $text = "" . shift;
287 22         36 my $value = shift;
288 22         38 my (@args) = ();
289              
290 22         39 local $Error::Depth = $Error::Depth + 1;
291              
292 22 100       187 @args = ( -file => $1, -line => $2 )
293             if ( $text =~
294             s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s );
295 22 100       64 push( @args, '-value', 0 + $value )
296             if defined($value);
297              
298 22         113 $self->SUPER::new( -text => $text, @args );
299             }
300              
301             sub stringify
302             {
303 12     12 1 59 my $self = shift;
304 12         41 my $text = $self->SUPER::stringify;
305 12 100       60 $text .= sprintf( " at %s line %d.\n", $self->file, $self->line )
306             unless ( $text =~ /\n$/s );
307 12         99 $text;
308             }
309              
310             ##########################################################################
311             ##########################################################################
312              
313             # Inspired by code from Jesse Glick and
314             # Peter Seibel
315              
316             package Error::subs;
317             $Error::subs::VERSION = '0.17030';
318 26     26   238 use Exporter ();
  26         37  
  26         788  
319 26     26   137 use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
  26         36  
  26         60669  
320              
321             @EXPORT_OK = qw(try with finally except otherwise);
322             %EXPORT_TAGS = ( try => \@EXPORT_OK );
323              
324             @ISA = qw(Exporter);
325              
326             sub run_clauses ($$$\@)
327             {
328 23     23   55 my ( $clauses, $err, $wantarray, $result ) = @_;
329 23         37 my $code = undef;
330              
331 23 100       106 $err = $Error::ObjectifyCallback->( { 'text' => $err } ) unless ref($err);
332              
333             CATCH:
334             {
335              
336             # catch
337 23         80 my $catch;
  23         36  
338 23 100       78 if ( defined( $catch = $clauses->{'catch'} ) )
339             {
340 14         22 my $i = 0;
341              
342             CATCHLOOP:
343 14         49 for ( ; $i < @$catch ; $i += 2 )
344             {
345 16         37 my $pkg = $catch->[$i];
346 16 100 66     178 unless ( defined $pkg )
347             {
348             #except
349 1         3 splice( @$catch, $i, 2, $catch->[ $i + 1 ]->($err) );
350 1         6 $i -= 2;
351 1         3 next CATCHLOOP;
352             }
353             elsif ( Scalar::Util::blessed($err) && $err->isa($pkg) )
354             {
355             $code = $catch->[ $i + 1 ];
356             while (1)
357             {
358             my $more = 0;
359             local ( $Error::THROWN, $@ );
360             my $ok = eval {
361             $@ = $err;
362             if ($wantarray)
363             {
364             @{$result} = $code->( $err, \$more );
365             }
366             elsif ( defined($wantarray) )
367             {
368             @{$result} = ();
369             $result->[0] = $code->( $err, \$more );
370             }
371             else
372             {
373             $code->( $err, \$more );
374             }
375             1;
376             };
377             if ($ok)
378             {
379             next CATCHLOOP if $more;
380             undef $err;
381             }
382             else
383             {
384             $err = $@ || $Error::THROWN;
385             $err = $Error::ObjectifyCallback->(
386             { 'text' => $err } )
387             unless ref($err);
388             }
389             last CATCH;
390             }
391             }
392             }
393             }
394              
395             # otherwise
396 13         21 my $owise;
397 13 100       64 if ( defined( $owise = $clauses->{'otherwise'} ) )
398             {
399 8         17 my $code = $clauses->{'otherwise'};
400 8         13 my $more = 0;
401 8         34 local ( $Error::THROWN, $@ );
402 8         16 my $ok = eval {
403 8         40 $@ = $err;
404 8 50       27 if ($wantarray)
    50          
405             {
406 0         0 @{$result} = $code->( $err, \$more );
  0         0  
407             }
408             elsif ( defined($wantarray) )
409             {
410 0         0 @{$result} = ();
  0         0  
411 0         0 $result->[0] = $code->( $err, \$more );
412             }
413             else
414             {
415 8         23 $code->( $err, \$more );
416             }
417 6         1670 1;
418             };
419 8 100       34 if ($ok)
420             {
421 6         16 undef $err;
422             }
423             else
424             {
425 2   33     18 $err = $@ || $Error::THROWN;
426              
427 2 50       12 $err = $Error::ObjectifyCallback->( { 'text' => $err } )
428             unless ref($err);
429             }
430             }
431             }
432 23         65 $err;
433             }
434              
435             sub try (&;$)
436             {
437 25     25   266582 my $try = shift;
438 25 100       78 my $clauses = @_ ? shift : {};
439 25         63 my $ok = 0;
440 25         42 my $err = undef;
441 25         54 my @result = ();
442              
443 25         61 unshift @Error::STACK, $clauses;
444              
445 25         52 my $wantarray = wantarray();
446              
447             do
448 25         39 {
449 25         74 local $Error::THROWN = undef;
450 25         35 local $@ = undef;
451              
452 25         48 $ok = eval {
453 25 50       77 if ($wantarray)
    100          
454             {
455 0         0 @result = $try->();
456             }
457             elsif ( defined $wantarray )
458             {
459 3         10 $result[0] = $try->();
460             }
461             else
462             {
463 22         54 $try->();
464             }
465 2         7 1;
466             };
467              
468 25 100 33     294 $err = $@ || $Error::THROWN
469             unless $ok;
470             };
471              
472 25         69 shift @Error::STACK;
473              
474 25 100       96 $err = run_clauses( $clauses, $err, wantarray, @result )
475             unless ($ok);
476              
477             $clauses->{'finally'}->()
478 25 100       109 if ( defined( $clauses->{'finally'} ) );
479              
480 25 100       63 if ( defined($err) )
481             {
482 12 100 66     86 if ( Scalar::Util::blessed($err) && $err->can('throw') )
483             {
484 11         27 throw $err;
485             }
486             else
487             {
488 1         11 die $err;
489             }
490             }
491              
492 13 50       124 wantarray ? @result : $result[0];
493             }
494              
495             # Each clause adds a sub to the list of clauses. The finally clause is
496             # always the last, and the otherwise clause is always added just before
497             # the finally clause.
498             #
499             # All clauses, except the finally clause, add a sub which takes one argument
500             # this argument will be the error being thrown. The sub will return a code ref
501             # if that clause can handle that error, otherwise undef is returned.
502             #
503             # The otherwise clause adds a sub which unconditionally returns the users
504             # code reference, this is why it is forced to be last.
505             #
506             # The catch clause is defined in Error.pm, as the syntax causes it to
507             # be called as a method
508              
509             sub with (&;$)
510             {
511 16     16   2529377 @_;
512             }
513              
514             sub finally (&)
515             {
516 3     3   36 my $code = shift;
517 3         9 my $clauses = { 'finally' => $code };
518 3         12 $clauses;
519             }
520              
521             # The except clause is a block which returns a hashref or a list of
522             # key-value pairs, where the keys are the classes and the values are subs.
523              
524             sub except (&;$)
525             {
526 1     1   490786 my $code = shift;
527 1   50     17 my $clauses = shift || {};
528 1   50     9 my $catch = $clauses->{'catch'} ||= [];
529              
530             my $sub = sub {
531 1     1   1 my $ref;
532 1         4 my (@array) = $code->( $_[0] );
533 1 50 33     13 if ( @array == 1 && ref( $array[0] ) )
534             {
535 1         2 $ref = $array[0];
536 1 50       6 $ref = [%$ref]
537             if ( UNIVERSAL::isa( $ref, 'HASH' ) );
538             }
539             else
540             {
541 0         0 $ref = \@array;
542             }
543 1         5 @$ref;
544 1         7 };
545              
546 1         2 unshift @{$catch}, undef, $sub;
  1         4  
547              
548 1         12 $clauses;
549             }
550              
551             sub otherwise (&;$)
552             {
553 10     10   952081 my $code = shift;
554 10   50     55 my $clauses = shift || {};
555              
556 10 50       46 if ( exists $clauses->{'otherwise'} )
557             {
558 0         0 require Carp;
559 0         0 Carp::croak("Multiple otherwise clauses");
560             }
561              
562 10         23 $clauses->{'otherwise'} = $code;
563              
564 10         39 $clauses;
565             }
566              
567             1;
568              
569             package Error::WarnDie;
570             $Error::WarnDie::VERSION = '0.17030';
571             sub gen_callstack($)
572             {
573 6     6   78 my ($start) = @_;
574              
575 6         1411 require Carp;
576 6         207 local $Carp::CarpLevel = $start;
577 6         7818 my $trace = Carp::longmess("");
578              
579             # Remove try calls from the trace
580 6         118 $trace =~
581             s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
582 6         129 $trace =~
583             s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
584 6         62 my @callstack = split( m/\n/, $trace );
585 6         58 return @callstack;
586             }
587              
588             my $old_DIE;
589             my $old_WARN;
590              
591             sub DEATH
592             {
593 4     4   4361689 my ($e) = @_;
594              
595 4   66 3   559 my $die = $old_DIE || sub { die @_ };
  3         4798  
596              
597 4 50       138 $die->(@_) if $^S;
598              
599 4         72 my ( $etype, $message, $location, @callstack );
600 4 100 66     271 if ( ref($e) && $e->isa("Error") )
601             {
602 1         11 $etype = "exception of type " . ref($e);
603 1         14 $message = $e->text;
604 1         28 $location = $e->file . ":" . $e->line;
605 1         11 @callstack = split( m/\n/, $e->stacktrace );
606             }
607             else
608             {
609             # Don't apply subsequent layer of message formatting
610 3 50       122 $die->($e) if ( $e =~ m/^\nUnhandled perl error caught at toplevel:\n\n/ );
611 3         76 $etype = "perl error";
612 3         31 my $stackdepth = 0;
613 3         80 while ( caller($stackdepth) =~ m/^Error(?:$|::)/ )
614             {
615 0         0 ++$stackdepth;
616             }
617              
618 3         86 @callstack = gen_callstack( $stackdepth + 1 );
619              
620 3         7 $message = "$e";
621 3         9 chomp $message;
622              
623 3 100       70 if ( $message =~ s/ at (.*?) line (\d+)\.$// )
624             {
625 2         26 $location = $1 . ":" . $2;
626             }
627             else
628             {
629 1         22 my @caller = caller($stackdepth);
630 1         6 $location = $caller[1] . ":" . $caller[2];
631             }
632             }
633              
634 4         6 shift @callstack;
635              
636             # Do it this way in case there are no elements; we don't print a spurious \n
637 4         17 my $callstack = join( "", map { "$_\n" } @callstack );
  8         33  
638              
639 4         81 $die->("\nUnhandled $etype caught at toplevel:\n\n $message\n\nThrown from: $location\n\nFull stack trace:\n\n$callstack\n");
640             }
641              
642             sub TAXES
643             {
644 3     3   2028287 my ($message) = @_;
645              
646 3   66 2   824 my $warn = $old_WARN || sub { warn @_ };
  2         103  
647              
648 3         194 $message =~ s/ at .*? line \d+\.$//;
649 3         51 chomp $message;
650              
651 3         146 my @callstack = gen_callstack(1);
652 3         11 my $location = shift @callstack;
653              
654             # $location already starts in a leading space
655 3         43 $message .= $location;
656              
657             # Do it this way in case there are no elements; we don't print a spurious \n
658 3         42 my $callstack = join( "", map { "$_\n" } @callstack );
  6         77  
659              
660 3         70 $warn->("$message:\n$callstack");
661             }
662              
663             sub import
664             {
665 14     14   40 $old_DIE = $SIG{__DIE__};
666 14         67 $old_WARN = $SIG{__WARN__};
667              
668 14         42 $SIG{__DIE__} = \&DEATH;
669 14         34 $SIG{__WARN__} = \&TAXES;
670             }
671              
672             1;
673              
674             __END__