File Coverage

lib/Fatal/Exception.pm
Criterion Covered Total %
statement 210 226 92.9
branch 80 118 67.8
condition 18 68 26.4
subroutine 34 35 97.1
pod n/a
total 342 447 76.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -c
2              
3             package Fatal::Exception;
4              
5             =head1 NAME
6              
7             Fatal::Exception - Succeed or throw exception
8              
9             =head1 SYNOPSIS
10              
11             use Fatal::Exception 'Exception::System' => qw< open close >;
12             open my $fh, "/nonexistent"; # throw Exception::System
13              
14             use Exception::Base 'Exception::My';
15             sub juggle { ... }
16             Fatal::Exception->import('Exception::My' => 'juggle');
17             juggle; # succeed or throw exception
18             Fatal::Exception->unimport('juggle');
19             juggle or die; # restore original behavior
20              
21             =head1 DESCRIPTION
22              
23             L provides a way to conveniently replace functions
24             which normally return a false value when they fail with equivalents
25             which raise exceptions if they are not successful. This is the same as
26             L module from Perl 5.8 and previous but it throws
27             L object on error.
28              
29             =cut
30              
31              
32 1     1   3197 use 5.006;
  1         2  
  1         29  
33 1     1   4 use strict;
  1         2  
  1         21  
34 1     1   13 use warnings;
  1         2  
  1         42  
35              
36             our $VERSION = 0.05;
37              
38              
39 1     1   4 use Symbol ();
  1         1  
  1         25  
40              
41              
42             use Exception::Base (
43 1         8 '+ignore_package' => __PACKAGE__,
44 1     1   4 );
  1         1  
45 1     1   434 use Exception::Argument;
  1         2  
  1         9  
46 1     1   297 use Exception::Fatal;
  1         2  
  1         7  
47              
48              
49             # Switch to enable dump for created wrapper functions
50             our $Debug;
51              
52              
53             # Cache for not fatalized functions. The key is "$sub".
54             our %Not_Fatalized_Functions;
55              
56              
57             # Cache for fatalized functions. The key is "$sub:$exception:$void".
58             our %Fatalized_Functions;
59              
60              
61             # Export the wrapped functions to the caller
62             sub import {
63 9     9   5481 my $pkg = shift;
64 9   100     1215 my $exception = shift || return;
65              
66 7 100       43 Exception::Argument->throw(
67             message => 'Not enough arguments for "' . __PACKAGE__ . '->import"',
68             ) unless @_;
69              
70 6   100     87 my $mod_version = $exception->VERSION || 0;
71 6 100       18 if (not $mod_version) {
72 1     1   364 eval "use $exception;";
  0         0  
  0         0  
  1         42  
73 1 50       5438 if ($@) {
74 1         13 Exception::Fatal->throw(
75             message => "Cannot find \"$exception\" exception class",
76             );
77             };
78             };
79              
80 5         6 my $callpkg = caller;
81 5         5 my $void = 0;
82              
83 5         10 foreach my $arg (@_) {
84 21 100       240 if ($arg eq ':void') {
85 4         6 $void = 1;
86             }
87             else {
88 17 100       43 my $sub = $arg =~ /::/
89             ? $arg
90             : $callpkg . '::' . $arg;
91 17         68 (my $name = $sub) =~ s/^&?(.*::)?//;
92              
93 17         37 __make_fatal(
94             exception => $exception,
95             name => $name,
96             pkg => $callpkg,
97             sub => $sub,
98             void => $void,
99             );
100             };
101             };
102              
103 4         46 return 1;
104             };
105              
106              
107             # Restore the non fatalized functions to the caller
108             sub unimport {
109 3     3   616 my $pkg = shift;
110              
111 3         4 my $callpkg = caller;
112              
113 3         5 foreach my $arg (@_) {
114 8 100       54 next if ($arg eq ':void');
115              
116 6 50       15 my $sub = $arg =~ /::/
117             ? $arg
118             : $callpkg . '::' . $arg;
119 6         23 (my $name = $sub) =~ s/^&?(.*::)?//;
120              
121 6         13 __make_not_fatal(
122             name => $name,
123             pkg => $callpkg,
124             sub => $sub
125             );
126             };
127              
128 3         11 return 1;
129             };
130              
131              
132             # Create the wrapper. Stolen from Fatal.
133             sub __make_fatal {
134             # args:
135             # exception - exception class name
136             # name - base name of sub
137             # pkg - current package name
138             # sub - full name of sub
139             # void - is function called in scalar context?
140 17     17   59 my (%args) = @_;
141              
142             # check args
143 68         85 Exception::Argument->throw(
144             message => 'Not enough arguments for "' . __PACKAGE__ . '->__make_fatal"',
145 17 50       29 ) if grep { not defined } @args{qw< exception name pkg sub >};
146              
147 17 50       64 Exception::Argument->throw(
148             message => 'Bad subroutine name for "' . __PACKAGE__ . '": ' . $args{name},
149             ) if not $args{name} =~ /^\w+$/;
150              
151 17         18 my ($proto, $code_proto, $call, $core, $argvs);
152 17 100       48 my $cache_key = "$args{sub}:$args{exception}:" . ($args{void} ? 1 : 0);
153 17 100 66     68 if (defined $Fatalized_Functions{$cache_key} and defined $Not_Fatalized_Functions{$args{sub}}) {
  9 100 100     40  
154             # already wrapped: restore from cache
155 8         5 undef *{ Symbol::qualify_to_ref($args{sub}) };
  8         20  
156 8         75 return *{ Symbol::qualify_to_ref($args{sub}) } = $Fatalized_Functions{$cache_key};
  8         19  
157             }
158 6         52 elsif (defined(&{$args{sub}}) and not eval { prototype "CORE::$args{name}" }) {
159             # user subroutine
160 4         969 $call = "&{\$" . __PACKAGE__ . "::Not_Fatalized_Functions{\"$args{sub}\"}}";
161 4         10 $proto = prototype $args{sub};
162 4 100       12 $Not_Fatalized_Functions{$args{sub}} = \&{$args{sub}}
  2         6  
163             unless defined $Not_Fatalized_Functions{$args{sub}};
164             }
165             else {
166             # CORE subroutine
167 5         8 $core = 1;
168 5         8 $call = "CORE::$args{name}";
169 5         6 $proto = eval { prototype $call };
  5         75  
170              
171             # not found as CORE subroutine
172 5 100       267 Exception::Argument->throw(
173             message => "\"$args{sub}\" is not a Perl subroutine",
174             ) unless $proto;
175              
176             # create package's function
177 4 100       4 if (not defined &{$args{sub}}) {
  4         15  
178             # not package's function yet
179 2         4 $argvs = __fill_argvs($proto);
180 2         4 my $name = "__$args{name}__Fatal__Exception__not_wrapped";
181 2         12 my $code = "package $args{pkg};\n"
182             . "sub $name ($proto) {\n"
183             . " no strict 'refs';\n"
184             . __write_invocation(
185             %args,
186             argvs => $argvs,
187             call => $call,
188             orig => 1,
189             )
190             . "}\n";
191 2 50       5 print STDERR $code if $Debug;
192              
193 1 50   1   5 eval $code;
  1 50   1   1  
  1 50   2   136  
  1     0   4  
  1         1  
  1         39  
  2         132  
  2         151  
  0         0  
  0         0  
  2         40  
  0         0  
  0         0  
194 2 50       6 if ($@) {
195 0         0 Exception::Fatal->throw(
196             message => "Cannot create \"$args{sub}\" subroutine",
197             );
198             };
199              
200 2         4 my $sub = "$args{pkg}::$name";
201 2 50       5 print STDERR "*{ $args{sub} } = \\&$sub;\n" if $Debug;
202 2         2 undef *{ Symbol::qualify_to_ref($args{sub}) };
  2         5  
203 2         24 *{ Symbol::qualify_to_ref($args{sub}) } = \&$sub;
  2         4  
204             };
205              
206 4 100       28 if (not defined $Not_Fatalized_Functions{$args{sub}}) {
207 2         2 $Not_Fatalized_Functions{$args{sub}} = \&{$args{sub}};
  2         7  
208             };
209             };
210              
211 8 100       17 if (defined $proto) {
212 4         7 $code_proto = " ($proto)";
213             }
214             else {
215 4         6 $code_proto = '';
216 4         5 $proto = '@';
217             };
218              
219 8 100       19 $argvs = __fill_argvs($proto) if not defined $argvs;
220              
221             # define new named subroutine (anonymous would be harder to debug from stacktrace)
222 8 100       71 my $name = "__$args{name}__Fatal__Exception__$args{exception}" . ($args{void} ? '_void' : '') . "__wrapped";
223 8         14 $name =~ tr/:/_/;
224              
225 8         39 my $code = "package $args{pkg};\n"
226             . "sub $name$code_proto {\n"
227             . " no strict 'refs';\n"
228             . __write_invocation(
229             %args,
230             argvs => $argvs,
231             call => $call,
232             )
233             . "}\n";
234 8 50       19 print STDERR $code if $Debug;
235              
236 1 0 0 1   4 my $newsub = eval $code;
  1 0 0 1   2  
  1 50 0 1   475  
  1 50 0 1   4  
  1 50 0 1   1  
  1 100 100 1   175  
  1 0 0 1   4  
  1 0 0 1   2  
  1 50 0 4   176  
  1 50 0 3   4  
  1 50 0 4   30  
  1 50 50 8   99  
  1 100 50 1   6  
  1 100 50 3   1  
  1 50 0 1   660  
  1 50 50 5   4  
  1 50 0     3  
  1 50 50     198  
  1   0     5  
  1   50     2  
  1   0     193  
  1   50     5  
  1         1  
  1         113  
  8         513  
  4         1289  
  0         0  
  0         0  
  3         6  
  1         8  
  3         331  
  0         0  
  0         0  
  3         6  
  0         0  
  4         1382  
  8         2825  
  1         418  
  3         223  
  1         350  
  5         1801  
237 8 50       58 if ($@) {
238 0         0 Exception::Fatal->throw(
239             message => "Cannot create \"$args{sub}\" subroutine",
240             );
241             };
242              
243 8         17 my $sub = "$args{pkg}::$name";
244 8 50       14 print STDERR "*{ $args{sub} } = \\&$sub;\n" if $Debug;
245              
246 8         7 undef *{ Symbol::qualify_to_ref($args{sub}) };
  8         19  
247 8         106 return *{ Symbol::qualify_to_ref($args{sub}) } = $Fatalized_Functions{$cache_key} = \&$sub;
  8         19  
248             };
249              
250              
251             # Restore the not-fatalized function.
252             sub __make_not_fatal {
253             # args:
254             # name - base name of sub
255             # pkg - current package name
256             # sub - full name of sub
257 6     6   15 my (%args) = @_;
258              
259             # check args
260 18         24 Exception::Argument->throw(
261             message => 'Not enough arguments for "' . __PACKAGE__ . '->__make_non_fatal"',
262 6 50       10 ) if grep { not defined } @args{qw< name pkg sub >};
263              
264 6 50       21 Exception::Argument->throw(
265             message => 'Bad subroutine name for "' . __PACKAGE__ . '": ' . $args{name},
266             ) if not $args{name} =~ /^\w+$/;
267              
268             # not wrapped - do nothing
269 6 100       16 return unless defined $Not_Fatalized_Functions{$args{sub}};
270              
271 4         5 undef *{ Symbol::qualify_to_ref($args{sub}) };
  4         9  
272 4         38 return *{ Symbol::qualify_to_ref($args{sub}) } = $Not_Fatalized_Functions{$args{sub}};
  4         10  
273             };
274              
275              
276             # Fill argvs array based on function prototype. Stolen from Fatal.
277             sub __fill_argvs {
278 8     8   11 my $proto = shift;
279              
280 8         9 my $n = -1;
281 8         9 my (@code, @protos, $seen_semi);
282              
283 8         24 while ($proto =~ /\S/) {
284 16         15 $n++;
285 16 100       21 if ($seen_semi) {
286 4         8 push(@protos,[$n,@code]);
287             };
288 16 50       29 if ($proto =~ s/^\s*\\([\@%\$\&])//) {
289 0         0 push(@code, $1 . "{\$_[$n]}");
290 0         0 next;
291             };
292 16 100       43 if ($proto =~ s/^\s*([*\$&])//) {
293 8         12 push(@code, "\$_[$n]");
294 8         21 next;
295             };
296 8 100       28 if ($proto =~ s/^\s*(;\s*)?\@//) {
297 6         17 push(@code, "\@_[$n..\$#_]");
298 6         7 last;
299             };
300 2 50       8 if ($proto =~ s/^\s*;//) {
301 2         1 $seen_semi = 1;
302 2         2 $n--;
303 2         6 next;
304             };
305 0         0 Exception::Argument->throw(
306             message => "Unknown prototype letters: \"$proto\"",
307             );
308             };
309 8         22 push @protos, [$n+1, @code];
310 8         19 return \@protos;
311             };
312              
313              
314             # Write subroutine invocation. Stolen from Fatal.
315             sub __write_invocation {
316             # args:
317             # argvs - ref to prototypes stored as array of array of calling arguments
318             # call - called sub full name
319             # exception - exception class name
320             # name - base name of sub
321             # orig - is function called as non-fatalized version?
322             # void - is function called in scalar context?
323 10     10   36 my (%args) = @_;
324              
325             # check args
326 40         48 Exception::Argument->throw(
327             message => 'Not enough arguments for "' . __PACKAGE__ . '->__write_invocation"',
328 10 50       18 ) if grep { not defined } @args{qw< argvs call exception name >};
329              
330 10         9 my @argvs = @{ $args{argvs} };
  10         17  
331              
332 10         12 my $code;
333              
334 10 100       16 if (@argvs == 1) {
335             # No optional arguments
336 7         7 my @argv = @{ $argvs[0] };
  7         12  
337 7         8 shift @argv;
338 7         21 $code =
339             " "
340             . __one_invocation(
341             %args,
342             argv => \@argv,
343             )
344             . ";\n";
345             }
346             else {
347 3         4 my $else = " ";
348 3         3 my (@out, @argv, $n);
349 3         7 while (@argvs) {
350 9         9 @argv = @{shift @argvs};
  9         20  
351 9         10 $n = shift @argv;
352 9         15 push @out, "${else}if (\@_ == $n) {\n";
353 9         9 $else = " }\n els";
354 9         24 push @out,
355             " return "
356             . __one_invocation(
357             %args,
358             argv => \@argv,
359             )
360             . ";\n";
361             }
362 3         9 push @out,
363             " };\n"
364             . " Exception::Argument->throw(\n"
365             . " ignore_level => 1,\n"
366             . " message => \"$args{name}: Do not expect to get \" . scalar \@_ . \" arguments\"\n"
367             . " );\n";
368 3         19 $code = join '', @out;
369             };
370              
371 10         53 return $code;
372             };
373              
374              
375             # Write subroutine invocation. Stolen from Fatal.
376             sub __one_invocation {
377             # args:
378             # argv - ref to prototypes stored as array of calling arguments
379             # call - called sub full name
380             # exception - exception class name
381             # name - base name of sub
382             # orig - is function called as non-fatalized version?
383             # void - is function called in scalar context?
384 16     16   49 my (%args) = @_;
385              
386             # check args
387 64         71 Exception::Argument->throw(
388             message => 'Not enough arguments for "' . __PACKAGE__ . '->__one_invocation"',
389 16 50       25 ) if grep { not defined } @args{qw< argv call exception name >};
390              
391 16         15 my $argv = join ', ', @{$args{argv}};
  16         26  
392              
393 16         14 my $code;
394              
395 16 100       30 if ($args{orig}) {
    100          
396 4         20 return "$args{call}($argv)";
397             }
398             elsif ($args{void}) {
399 2         17 $code = "(defined wantarray)\n"
400             . " ? $args{call}($argv)\n"
401             . " : do {\n"
402             . " my \$return = eval {\n"
403             . " $args{call}($argv);\n"
404             . " };\n"
405             . " if (\$@) {\n"
406             . " Exception::Fatal->throw(\n"
407             . " ignore_level => 1,\n"
408             . " message => \"Cannot $args{name}\",\n"
409             . " );\n"
410             . " };\n"
411             . " \$return;\n"
412             . " } || $args{exception}->throw(\n"
413             . " ignore_level => 1,\n"
414             . " message => \"Cannot $args{name}\",\n"
415             . " )";
416             }
417             else {
418 10         42 $code = "$args{call}($argv)\n"
419             . " || $args{exception}->throw(\n"
420             . " ignore_level => 1,\n"
421             . " message => \"Cannot $args{name}\"\n"
422             . " )";
423 10         84 $code = "(defined wantarray)\n"
424             . " ? do {\n"
425             . " my \@return = eval {\n"
426             . " $args{call}($argv);\n"
427             . " };\n"
428             . " if (\$@) {\n"
429             . " Exception::Fatal->throw(\n"
430             . " ignore_level => 1,\n"
431             . " message => \"Cannot $args{name}\",\n"
432             . " );\n"
433             . " };\n"
434             . " \@return;\n"
435             . " } || $args{exception}->throw(\n"
436             . " ignore_level => 1,\n"
437             . " message => \"Cannot $args{name}\",\n"
438             . " )\n"
439             . " : do {\n"
440             . " my \$return = eval {\n"
441             . " $args{call}($argv);\n"
442             . " };\n"
443             . " if (\$@) {\n"
444             . " Exception::Fatal->throw(\n"
445             . " ignore_level => 1,\n"
446             . " message => \"Cannot $args{name}\",\n"
447             . " );\n"
448             . " };\n"
449             . " \$return;\n"
450             . " } || $args{exception}->throw(\n"
451             . " ignore_level => 1,\n"
452             . " message => \"Cannot $args{name}\",\n"
453             . " )";
454             };
455              
456 12         68 return $code;
457             };
458              
459              
460             1;
461              
462              
463             __END__