File Coverage

blib/lib/Exception/SEH.pm
Criterion Covered Total %
statement 176 184 95.6
branch 75 84 89.2
condition 31 35 88.5
subroutine 21 21 100.0
pod 1 5 20.0
total 304 329 92.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package Exception::SEH;
3              
4 24     24   678959 use strict;
  24         62  
  24         874  
5 24     24   505 use 5.008001;
  24         79  
  24         798  
6              
7 24     24   136 use Carp ();
  24         43  
  24         423  
8 24     24   21903 use Devel::Declare ();
  24         195437  
  24         683  
9 24     24   24042 use B::Hooks::EndOfScope;
  24         376698  
  24         169  
10 24     24   15642 use Exception::SEH::Parser;
  24         79  
  24         945  
11 24     24   170 use Scalar::Util qw(blessed);
  24         42  
  24         1301  
12 24     24   165 use Scope::Upper qw(unwind want_at reap :words);
  24         69  
  24         4570  
13              
14 24     24   131 use XSLoader;
  24         39  
  24         973  
15              
16             BEGIN{
17 24     24   130 no strict 'refs';
  24         43  
  24         1236  
18 24     24   66 foreach (qw(INITIAL TRY CATCH FINALLY DEBUG)) {
19 120         134 *{'Exception::SEH::'.$_} = *{'Exception::SEH::Parser::'.$_};
  120         7272  
  120         346  
20             }
21             }
22              
23             our $VERSION = '0.0202';
24             $Carp::Internal{'Exception::SEH'}++;
25             $Carp::Internal{'Devel::Declare'}++;
26              
27             our %OPTS = ();
28             our $params = [];
29             our $need_unwind = 0;
30             our $hook_nested_level = 0;
31             our $parse_catch_called = 0;
32             our $return_hook_id = undef;
33              
34             XSLoader::load(__PACKAGE__, $VERSION);
35              
36             sub import{
37 26     26   380 my $class = shift;
38 26         130 my $caller = caller;
39              
40 26         501 my $cur_opts = $OPTS{$caller} = {
41             -nosig => 0,
42             -noret => 0,
43             -safetry => 0,
44             };
45              
46 26         87 foreach (@_) {
47 5 100       22 if (exists $cur_opts->{$_}){
48 4         15 $cur_opts->{$_} = 1;
49             }else{
50 1         27 Carp::croak "Tried to set non-existent option: $_";
51             }
52             }
53              
54             #try
55             Devel::Declare->setup_for(
56 25         283 $caller,
57             { try => { const => \&parse_try } }
58             );
59              
60 24     24   127 no strict 'refs';
  24         48  
  24         43092  
61              
62             #this'd be shadowed
63 25         687 *{$caller.'::try'} = \&try;
  25         243  
64              
65             #these are never directly called
66 25     2   133 *{$caller.'::finally'} = sub (;&) { Carp::croak 'Found finally without a try{} block' };
  25         128  
  2         26  
67 25     1   83 *{$caller.'::catch'} = sub (;&) { Carp::croak 'Found catch without a try{} block' };
  25         17657  
  1         29  
68             }
69              
70             sub parse_try{
71 132     132 0 69660 my $parser = Exception::SEH::Parser->new($_[1]);
72              
73 132 50       557 if ((my $token = $parser->get_word()) ne 'try'){
74 0         0 return;
75             }
76              
77 132         463 $parser->skip_word();
78 132         350 $parser->skip_spaces();
79 132 50       526 if ($parser->get_symbols(2) eq '=>'){
80 0         0 return;
81             }
82              
83 132         428 $parser->inject('(\@_, sub');
84              
85 132 100 66     1349 $return_hook_id = Exception::SEH::XS::install_return_op_check()
86             if !$OPTS{Devel::Declare::get_curstash_name}->{'-noret'} && !$return_hook_id;
87 132         151 $hook_nested_level++;
88              
89 132         441 $parser->inject_if_block($parser->get_injector('Exception::SEH::aftercheck', TRY));
90 129         443 $parser->inject('*_ = $Exception::SEH::params;');
91             }
92              
93             sub aftercheck{
94 220     220 0 16812 my $prev_item_type = shift;
95              
96             on_scope_end {
97 220 100 100 220   10591 if (
98             $return_hook_id
99             &&
100             --$hook_nested_level <= 0
101             ){
102 157         500 Exception::SEH::XS::uninstall_return_op_check($return_hook_id);
103 157         226 $return_hook_id = undef;
104             }
105              
106 220         955 my $parser = Exception::SEH::Parser->new(Devel::Declare::get_linestr_offset);
107              
108 220         843 $parser->skip_spaces();
109 220         1315 my $token = $parser->get_word();
110              
111 220 100       628 if ($token eq 'catch'){
    100          
112 95         200 parse_catch($parser, $prev_item_type);
113             }elsif ($token eq 'finally'){
114 26         72 parse_finally($parser, $prev_item_type);
115             }else{
116 99         301 $parser->inject(');');
117             }
118             }
119 220         1519 }
120              
121             sub parse_finally{
122 26     26 0 48 my ($parser, $prev_item_type) = @_;
123              
124 26 100       99 if ($prev_item_type == FINALLY){
125 4         19 $parser->panic('Found finally block more than once after single try{}');
126             }
127              
128 22         86 $parser->cutoff(length('finally'));
129 22         73 $parser->inject(', sub ');
130 22         80 $parser->skip_spaces();
131 22         81 $parser->inject_if_block($parser->get_injector('Exception::SEH::aftercheck', FINALLY));
132             }
133              
134             sub parse_catch{
135 95     95 0 144 my ($parser, $prev_item_type) = @_;
136              
137 95 50       261 if ($prev_item_type == FINALLY){
138 0         0 $parser->panic('Found catch block again after finally');
139             }
140              
141 95         276 $parser->cutoff(length('catch'));
142 95         261 $parser->inject(', ');
143 95         363 $parser->skip_spaces();
144              
145 95         116 my $err_var = undef;
146              
147 95 100       336 if ($parser->get_symbols(1) eq '('){
148 66         168 my $args = $parser->extract_args();
149              
150 66 100       138 if (length($args)){
151 63         286 my ($type) = $args =~ /\G\s*(?!where\W)([a-zA-Z0-9_:]+)\s*/gcs;
152 63         281 ($err_var) = $args =~ /\G\s*\$(\w+)\s*/gcs;
153              
154 63 100 100     247 if ($type && !$err_var){
155 5         13 $parser->panic("Found exception class definition, but no context var while parsing catch definition");
156             }
157              
158 58 100       103 if ($type){
159 26         109 $parser->inject(" '$type', ");
160             }else{
161 32         95 $parser->inject(" undef, ");
162             }
163              
164 58         179 my ($where_present, $where) = $args =~ /\G\s*(where)\s*(.*?)\s*$/gcs;
165 58 100 100     219 if ($where_present && $where !~ /^{.*}$/){
166 10         21 $parser->panic('"Where" must be followed by a block inside catch definition');
167             }
168              
169 48 100       86 if ($where){
170 9         49 $parser->inject(" sub $where");
171             }else{
172 39         114 $parser->inject(" undef");
173             }
174              
175 48 100       161 if (pos($args) != length($args)){
176 4         9 $parser->panic('Found junk inside catch definition');
177             }
178              
179             }else{
180 3         48 $parser->inject(' undef, undef');
181             }
182              
183             }else{
184 29         87 $parser->inject(' undef, undef');
185             }
186              
187 76         224 $parser->inject(', sub ');
188 76 100 33     596 $return_hook_id = Exception::SEH::XS::install_return_op_check()
189             if !$OPTS{Devel::Declare::get_curstash_name}->{'-noret'} && !$return_hook_id;
190 76         90 $hook_nested_level++;
191              
192 76         209 $parser->skip_spaces();
193 76         232 $parser->inject_if_block($parser->get_injector('Exception::SEH::aftercheck', CATCH));
194 70 100       393 $parser->inject('my $'.$err_var.' = $@;') if $err_var;
195             }
196              
197             #==========##==========##==========#
198              
199             sub try($&@) {
200 128     128 1 40060 my $opts = $OPTS{scalar caller};
201 128 100       4295 local $SIG{__DIE__} = 'DEFAULT' if $opts->{'-nosig'};
202              
203             #for unwind
204 128         228 my $is_top_try = 0;
205              
206 128 100 100     1244 if (!defined(EVAL) || EVAL != CALLER 2){ #we're inside top try
207 104         152 $is_top_try = 1;
208             }
209 128         219 local $params = shift;
210 128         245 my $code = shift;
211              
212 128 100       354 my $finally = (scalar @_ % 3 == 0 ? undef : pop @_);
213 128         204 my $catch_fail = 0;
214 128         151 my $exception_caught = 1;
215              
216 128 100       489 my $cx = $is_top_try ? UP SUB : EVAL;
217 128 100       664 my $context = $opts->{'-noret'} ? wantarray : want_at($cx);
218              
219 128         158 $@ = undef;
220 128         253 my @result;
221 128 50       329 if ($context){
    100          
222 0         0 @result = eval { $code->() }
  0         0  
223             }elsif(defined($context)){
224 60         78 $result[0] = eval { $code->() }
  60         804  
225             }else{
226 68         143 eval { $code->() }
  68         280  
227             }
228              
229 128         2214 my $err = $@;
230 128 100       476 $exception_caught = 0 if $err;
231              
232 128 100 100     387 if ($err && scalar @_){
233 31         90 my $checked = 0;
234              
235             eval{
236 31         98 my $err_blessed = blessed($err);
237              
238 31         89 my $pos = -3;
239 31         87 while ($pos + 3 < scalar @_) {
240 48         71 $pos += 3;
241 48         131 my ($type, $where, $handler) = @_[$pos..$pos+2];
242              
243 48 100 100     318 if (
      66        
244             !$type
245             ||
246             $err_blessed
247             &&
248             $err->isa($type)
249             ){
250 29 100       69 if (defined $where){
251 9         18 local $_ = $err;
252 9 100       25 next if !$where->();
253             }
254              
255 24         54 $exception_caught = 1;
256 24         29 my $_need_unwind = $need_unwind;
257 24         33 $need_unwind = 0;
258 24         30 my @_result;
259              
260             eval {
261 24         27 $@ = $err;
262 24 50       87 if ($context){
    100          
263 0         0 @_result = $handler->(@$params);
264             } elsif (defined($context)) {
265 2         8 $_result[0] = $handler->(@$params);
266             } else {
267 22         58 $handler->(@$params);
268             };
269 20         105 1;
270 24 100       34 } or do {
271 4         33 $err = $@;
272 4         7 $catch_fail = 1;
273             };
274              
275 24 50       53 if ($need_unwind){
276 0         0 @result = @_result;
277             }else{
278 24         32 $need_unwind = $_need_unwind;
279             }
280 24         43 last;
281             }
282             }
283 29         92 1;
284 31 100       44 } or do {
285 2         15 $err = $@;
286 2         4 $catch_fail = 1;
287             }
288             }
289              
290 128 100       320 if (defined $finally){
291 26         40 $@ = $err;
292 26         245 $finally->(@$params);
293             }
294              
295 124 100 100     817 if ($catch_fail || (!$exception_caught && !$opts->{'-safetry'})){
      100        
296 21         99 local $SIG{__DIE__} = 'DEFAULT';
297 21         164 die $err;
298             }
299              
300 103 100 100     502 if($opts->{'-noret'} || !$need_unwind){
    50          
301             #print STDERR "normal return\n";
302 99         136 $need_unwind = 0;
303 99 50       1056 return wantarray ? @result : $result[0];
304              
305             }elsif($need_unwind){
306 4 100       12 $need_unwind = 0 if $is_top_try;
307 4 50       36 unwind +($context ? @result : $result[0]) => $cx;
308              
309             }else{
310 0           Carp::croak 'Cannot determine return point';
311             }
312             }
313              
314             1;
315              
316             =head1
317              
318             Copyright (c) 2009 by Sergey Aleynikov.
319             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
320              
321             =cut