File Coverage

blib/lib/Devel/Confess.pm
Criterion Covered Total %
statement 216 280 77.1
branch 99 158 62.6
condition 35 76 46.0
subroutine 32 37 86.4
pod n/a
total 382 551 69.3


line stmt bran cond sub pod time code
1             package Devel::Confess;
2             BEGIN {
3 9     9   214112 my $can_use_informative_names = "$]" >= 5.008;
4             # detect -d:Confess. disable debugger features for now. we'll
5             # enable them when we need them.
6 9 50 33     131 if (!defined &DB::DB && $^P & 0x02) {
7 0         0 $can_use_informative_names = 1;
8 0         0 $^P = 0;
9             }
10             *_CAN_USE_INFORMATIVE_NAMES
11 9 50       264 = $can_use_informative_names ? sub () { 1 } : sub () { 0 };
12             }
13              
14 9     9   247 use 5.006;
  9         27  
15 9     9   51 use strict;
  9         13  
  9         223  
16 9     9   33 use warnings;
  9         15  
  9         315  
17 9     9   37 no warnings 'once';
  9         14  
  9         565  
18              
19             our $VERSION = '0.009003';
20             $VERSION = eval $VERSION;
21              
22 9     9   50 use Carp ();
  9         11  
  9         166  
23 9     9   4742 use Symbol ();
  9         7203  
  9         345  
24 9         838 use Devel::Confess::_Util qw(
25             blessed
26             refaddr
27             weaken
28             longmess
29             _str_val
30             _in_END
31             _can_stringify
32             _can
33             _isa
34 9     9   4410 );
  9         18  
35 9     9   48 use Config ();
  9         20  
  9         2764  
36             BEGIN {
37             *_BROKEN_CLONED_DESTROY_REBLESS
38 9 50 33 9   151 = ("$]" >= 5.008009 && "$]" < 5.010000) ? sub () { 1 } : sub () { 0 };
39             *_BROKEN_CLONED_GLOB_UNDEF
40 9 50 33     118 = ("$]" > 5.008009 && "$]" <= 5.010000) ? sub () { 1 } : sub () { 0 };
41             *_BROKEN_SIG_DELETE
42 9 50       46 = ("$]" < 5.008008) ? sub () { 1 } : sub () { 0 };
43             *_DEBUGGING
44             = (
45             defined &Config::non_bincompat_options
46             ? (grep $_ eq 'DEBUGGING', Config::non_bincompat_options())
47             : ($Config::Config{ccflags} =~ /-DDEBUGGING\b/)
48 9 50       216 ) ? sub () { 1 } : sub () { 0 };
    50          
49 9         24 my $inf = 9**9**9;
50 0         0 *_INF = sub () { $inf }
51 9         10173 }
52              
53             $Carp::Internal{+__PACKAGE__}++;
54              
55             our %NoTrace;
56             $NoTrace{'Throwable::Error'}++;
57             $NoTrace{'Moose::Error::Default'}++;
58              
59             our %OPTIONS = (
60             objects => !!1,
61             builtin => undef,
62             dump => !!0,
63             color => !!0,
64             source => 0,
65             evalsource => 0,
66             errors => !!1,
67             warnings => !!1,
68             better_names => !!1,
69             );
70             our %ENABLEOPTS = (
71             dump => 3,
72             source => 3,
73             evalsource => 3,
74             );
75             our %NUMOPTS = (
76             dump => 1,
77             source => 1,
78             evalsource => 1,
79             );
80              
81             our @options = sort keys %OPTIONS;
82             our ($opt_match) =
83             map qr/^-?(?:(no[_-]?)(?:$_)|(?:$_)(?:(\d+)|=(.*)|))$/,
84             join '|',
85             map {
86             my $o = $_;
87             $o =~ s/_/[-_]?/g;
88             '('.$o.')';
89             }
90             @options;
91              
92             sub _parse_options {
93 53     53   1460 my %opts;
94             my @bad;
95 53         160 while (@_) {
96 53         80 my $arg = shift;
97 53 100       582 my @match = defined $arg ? $arg =~ $opt_match : ();
98 53 100       137 if (@match) {
99 47         63 my $no = shift @match;
100 47         55 my $equal = pop @match;
101 47         48 my $num = pop @match;
102 47         334 my ($opt) =
103             map $options[$_ % @options],
104             grep defined $match[$_],
105             0 .. $#match;
106             my $value
107             = defined $no ? !!0
108             : defined $equal ? $equal
109             : defined $num ? $num
110             : @_ && (!defined $_[0] || $_[0] =~ /^\d+$/) ? shift
111 47 100 66     266 : defined $ENABLEOPTS{$opt} ? $ENABLEOPTS{$opt}
    100          
    100          
    100          
    100          
112             : !!1;
113              
114 47 100       116 if ($NUMOPTS{$opt}) {
115 16 100       47 $value
    50          
116             = !defined $value ? 0
117             : !$value ? _INF
118             : 0+$value;
119             }
120 47         217 $opts{$opt} = $value;
121             }
122             else {
123 6         21 push @bad, $arg;
124             }
125             }
126 53 100       121 if (@bad) {
127 5         19 local $SIG{__DIE__};
128 5 100       21 Carp::croak("invalid options: " . join(', ', map { defined $_ ? $_ : '[undef]' } @bad));
  6         570  
129             }
130 48         165 \%opts;
131             }
132              
133             if (my $env = $ENV{DEVEL_CONFESS_OPTIONS}) {
134             local $@;
135             eval {
136             my $options = _parse_options(grep length, split /[\s,]+/, $env);
137             @OPTIONS{keys %$options} = values %$options;
138             1;
139             } or warn "DEVEL_CONFESS_OPTIONS: $@";
140             }
141              
142             our %OLD_SIG;
143              
144             sub import {
145 34     34   35159 my $class = shift;
146              
147 34         135 my $options = _parse_options(@_);
148 34         129 @OPTIONS{keys %$options} = values %$options;
149              
150 34 50       99 if (defined $OPTIONS{builtin}) {
151 0         0 require Devel::Confess::Builtin;
152 0 0       0 my $do = $OPTIONS{builtin} ? 'import' : 'unimport';
153 0         0 Devel::Confess::Builtin->$do;
154             }
155 34 100 66     142 if ($OPTIONS{source} || $OPTIONS{evalsource}) {
156 10         67281 require Devel::Confess::Source;
157 10         57 Devel::Confess::Source->import;
158             }
159 34 50 33     121 if ($OPTIONS{color} && $^O eq 'MSWin32') {
160 0 0       0 if (eval { require Win32::Console::ANSI }) {
  0         0  
161 0         0 Win32::Console::ANSI->import;
162             }
163             else {
164 0         0 local $SIG{__WARN__};
165 0         0 Carp::carp
166             "Devel::Confess color option requires Win32::Console::ANSI on Windows";
167 0         0 $OPTIONS{color} = 0;
168             }
169             }
170              
171 34 50 33     228 if ($OPTIONS{errors} && !$OLD_SIG{__DIE__}) {
172             $OLD_SIG{__DIE__} = $SIG{__DIE__}
173 34 100 100     138 if $SIG{__DIE__} && $SIG{__DIE__} ne \&_die;
174 34         128 $SIG{__DIE__} = \&_die;
175             }
176 34 100 66     156 if ($OPTIONS{warnings} && !$OLD_SIG{__WARN__}) {
177             $OLD_SIG{__WARN__} = $SIG{__WARN__}
178 16 50 66     64 if $SIG{__WARN__} && $SIG{__WARN__} ne \&_warn;
179 16         53 $SIG{__WARN__} = \&_warn;
180             }
181              
182             # enable better names for evals and anon subs
183             $^P |= 0x100 | 0x200
184 34 50       6255 if _CAN_USE_INFORMATIVE_NAMES && $OPTIONS{better_names};
185             }
186              
187             sub unimport {
188 27     27   3734 for my $sig (
189             [ __DIE__ => \&_die ],
190             [ __WARN__ => \&_warn ],
191             ) {
192 54         92 my ($name, $sub) = @$sig;
193 54 100       194 my $now = $SIG{$name} or next;
194 36         47 my $old = $OLD_SIG{$name};
195 36 50 33     153 if ($now ne $sub && $old) {
    100          
196 0         0 local $SIG{__WARN__};
197 0         0 warn "Can't restore $name handler!\n";
198 0         0 delete $SIG{$sig};
199             }
200             elsif ($old) {
201 5         9 $SIG{$name} = $old;
202 5         13 delete $OLD_SIG{$name};
203             }
204             else {
205 9     9   57 no warnings 'uninitialized'; # bogus warnings on perl < 5.8.8
  9         14  
  9         1360  
206 31         29 undef $SIG{$name}
207             if _BROKEN_SIG_DELETE;
208 31         111 delete $SIG{$name};
209             }
210             }
211             }
212              
213             sub _find_sig {
214 61     61   164 my $sig = $_[0];
215             return undef
216 61 100       241 if !defined $sig;
217 5 100       13 return $sig
218             if ref $sig;
219             return undef
220 4 100 100     22 if $sig eq 'DEFAULT' || $sig eq 'IGNORE';
221             # this isn't really needed because %SIG entries are always fully qualified
222             package #hide
223             main;
224 9     9   55 no strict 'refs';
  9         24  
  9         4862  
225 2 50       3 defined &{$sig} ? \&{$sig} : undef;
  2         11  
  2         8  
226             }
227              
228             sub _warn {
229 2     2   7 local $SIG{__WARN__};
230 2         10 my @convert = _convert(@_);
231 0 0       0 if (my $sig = _find_sig($OLD_SIG{__WARN__})) {
232 0 0       0 (\&$sig)->(ref $convert[0] ? $convert[0] : join('', @convert));
233             }
234             else {
235 0         0 @convert = _ex_as_strings(@convert);
236 0 0       0 @convert = _colorize(33, @convert) if $OPTIONS{color};
237 0         0 warn @convert;
238             }
239             }
240             sub _die {
241 63     63   7906 local $SIG{__DIE__};
242 63         185 my @convert = _convert(@_);
243 61 100       285 if (my $sig = _find_sig($OLD_SIG{__DIE__})) {
244 3 50       17 (\&$sig)->(ref $convert[0] ? $convert[0] : join('', @convert));
245             }
246 61 50       1920 @convert = _ex_as_strings(@convert) if _can_stringify;
247 61 50 33     187 @convert = _colorize(31, @convert) if $OPTIONS{color} && _can_stringify;
248 61         63 if (_DEBUGGING && _in_END) {
249             local $SIG{__WARN__};
250             warn @convert;
251             $! ||= 1;
252             return;
253             }
254 61 100       1172 die @convert unless ref $convert[0];
255             }
256              
257             sub _colorize {
258 0     0   0 my ($color, @convert) = @_;
259 0 0 0     0 if ($OPTIONS{color} eq 'force' || -t *STDERR) {
260 0 0       0 if (@convert == 1) {
261 0         0 $convert[0] = s/(.*)//;
262 0         0 unshift @convert, $1;
263             }
264 0         0 $convert[0] = "\e[${color}m$convert[0]\e[m";
265             }
266 0         0 return @convert;
267             }
268              
269             sub _ref_formatter {
270 15     15   2105 require Data::Dumper;
271 15     0   7655 local $SIG{__WARN__} = sub {};
272 15     0   52 local $SIG{__DIE__} = sub {};
273 9     9   55 no warnings 'once';
  9         10  
  9         1393  
274 15         17 local $Data::Dumper::Indent = 0;
275 15         15 local $Data::Dumper::Purity = 0;
276 15         13 local $Data::Dumper::Terse = 1;
277 15         11 local $Data::Dumper::Useqq = 1;
278 15 100       81 local $Data::Dumper::Maxdepth = $OPTIONS{dump} == _INF ? 0 : $OPTIONS{dump};
279 15         40 Data::Dumper::Dumper($_[0]);
280             }
281              
282             sub _stack_trace {
283 9     9   47 no warnings 'once';
  9         22  
  9         5014  
284             local $Carp::RefArgFormatter
285 65 100   65   275 = $OPTIONS{dump} ? \&_ref_formatter : \&_str_val;
286 65         2214 my $message = &longmess;
287 61         837 $message =~ s/\.?$/./m;
288 61 100 66     310 if ($OPTIONS{source} || $OPTIONS{evalsource}) {
289             $message .= Devel::Confess::Source::source_trace(1,
290 21 50       91 $OPTIONS{evalsource} ? ($OPTIONS{evalsource}, 1) : $OPTIONS{source});
291             }
292 61         143 $message;
293             }
294              
295             # these are package varibles to control their lifetime. they should not be
296             # used externally.
297             our $PACK_SUFFIX = 'A000';
298              
299             our %EXCEPTIONS;
300             our %PACKAGES;
301             our %MESSAGES;
302             our %CLONED;
303              
304             sub CLONE {
305             my %id_map = map {
306 1     1   29 my $ex = $EXCEPTIONS{$_};
  1         4  
307 1 50       15 defined $ex ? ($_ => refaddr($ex)) : ();
308             } keys %EXCEPTIONS;
309              
310 1         6 %EXCEPTIONS = map {; $id_map{$_} => $EXCEPTIONS{$_}} keys %id_map;
  1         7  
311 1         4 %PACKAGES = map {; $id_map{$_} => $PACKAGES{$_}} keys %id_map;
  1         5  
312 1         5 %MESSAGES = map {; $id_map{$_} => $MESSAGES{$_}} keys %id_map;
  1         4  
313 1         3 %CLONED = map {; $_ => 1 } values %id_map
314             if _BROKEN_CLONED_DESTROY_REBLESS || _BROKEN_CLONED_GLOB_UNDEF;
315             weaken($_)
316 1         10 for values %EXCEPTIONS;
317             }
318              
319             sub _update_ex_refs {
320 65     65   212 for my $id ( keys %EXCEPTIONS ) {
321             next
322 0 0       0 if defined $EXCEPTIONS{$id};
323 0         0 delete $EXCEPTIONS{$id};
324 0         0 delete $PACKAGES{$id};
325 0         0 delete $MESSAGES{$id};
326 0         0 delete $CLONED{$id}
327             if _BROKEN_CLONED_DESTROY_REBLESS || _BROKEN_CLONED_GLOB_UNDEF;
328             }
329             }
330              
331             sub _convert {
332 65     65   155 _update_ex_refs;
333 65 100       550 if (my $class = blessed(my $ex = $_[0])) {
    50          
334             return @_
335 3 50       23 unless $OPTIONS{objects};
336             return @_
337 9 50   9   58 if ! do {no strict 'refs'; defined &{"Devel::Confess::_Attached::DESTROY"} };
  9         16  
  9         2844  
  3         136  
  3         7  
  3         48  
338 3         9 my $message;
339 3         27 my $id = refaddr($ex);
340 3 50       15 if (defined $EXCEPTIONS{$id}) {
341             return @_
342 0 0       0 if _isa($ex, "Devel::Confess::_Attached");
343              
344             # something is going very wrong. possibly from a Safe compartment.
345             # we probably broke something, but do the best we can.
346 0 0       0 if ((ref $ex) =~ /^Devel::Confess::__ANON_/) {
347 0         0 my $oldclass = $PACKAGES{$id};
348 0         0 $message = $MESSAGES{$id};
349 0         0 bless $ex, $oldclass;
350             }
351             else {
352             # give up
353 0         0 return @_;
354             }
355             }
356              
357 3   50     80 my $does = _can($ex, 'can') && ($ex->can('does') || $ex->can('DOES')) || sub () { 0 };
358 3 50       11 if (
359             grep {
360 6 50 33     205 $NoTrace{$_}
      33        
361             && _can($ex, 'isa')
362             && $ex->isa($_)
363             || $ex->$does($_)
364             } keys %NoTrace
365             ) {
366 0         0 return @_;
367             }
368              
369 3   33     23 $message ||= _stack_trace();
370              
371 3         13 weaken($EXCEPTIONS{$id} = $ex);
372 3         5 $PACKAGES{$id} = $class;
373 3         6 $MESSAGES{$id} = $message;
374              
375 3         11 my $newclass = __PACKAGE__ . '::__ANON_' . $PACK_SUFFIX++ . '__';
376              
377             {
378 9     9   47 no strict 'refs';
  9         13  
  9         9467  
  3         3  
379 3         5 @{$newclass . '::ISA'} = ('Devel::Confess::_Attached', $class);
  3         58  
380             }
381              
382 3         22 bless $ex, $newclass;
383 3         10 return $ex;
384             }
385             elsif (ref($ex = $_[0])) {
386 0         0 my $id = refaddr($ex);
387              
388 0         0 my $message = _stack_trace;
389              
390 0         0 weaken($EXCEPTIONS{$id} = $ex);
391 0         0 $PACKAGES{$id} = undef;
392 0   0     0 $MESSAGES{$id} ||= $message;
393              
394 0         0 return $ex;
395             }
396              
397 62         187 my $out = join('', @_);
398              
399 62 100       231 if (caller(1) eq 'Carp') {
400 24         637 my $long = longmess();
401 24         43 my $long_trail = $long;
402 24         128 $long_trail =~ s/.*?\n//;
403 24 100       9087 $out =~ s/\Q$long\E\z|\Q$long_trail\E\z//
404             or $out =~ s/(.*) at .*? line .*?\n\z/$1/;
405             }
406              
407 62         97 my $source_trace;
408             $out =~ s/^(={75}\ncontext for .*^={75}\n\z)//ms
409             and $source_trace = $1
410 62 100 66     432 if $OPTIONS{source} || $OPTIONS{evalsource};
      66        
411 62         140 my $trace = _stack_trace();
412 58         247 $trace =~ s/^(.*\n?)//;
413 58         142 my $where = $1;
414 58         58 my $new_source_trace;
415             $trace =~ s/^(={75}\ncontext for .*^={75}\n\z)//ms
416             and $new_source_trace = $1
417 58 100 33     389 if $OPTIONS{source} || $OPTIONS{evalsource};
      66        
418 58         89 my $find = $where;
419 58         198 $find =~ s/(\.?\n?)\z//;
420 58 50       394 my $trace_re = length $trace ? "(?:\Q$trace\E)?" : '';
421 58 100       3051 $out =~ s/(\Q$find\E(?: during global destruction)?(\.?\n?))$trace_re\z//
422             and $where = $1;
423 58 100       157 if (defined $source_trace) {
424 9 50       28 if (defined $new_source_trace) {
425 9         50 $new_source_trace =~ s/^={75}\n//;
426 9         1084 $source_trace =~ s/^(([-=])\2{74}\n)(?:\Q$new_source_trace\E)?\z/$1/ms;
427             }
428 9         28 $trace .= $source_trace;
429             }
430 58 100       130 if (defined $new_source_trace) {
431 21         75 $trace .= $new_source_trace;
432             }
433 58         295 return ($out, $where . $trace);
434             }
435              
436             sub _ex_as_strings {
437 1     1   4 my $ex = $_[0];
438             return @_
439 1 50       4 unless ref $ex;
440 1         6 my $id = refaddr($ex);
441 1         4 my $class = $PACKAGES{$id};
442 1         3 my $message = $MESSAGES{$id};
443 1         2 my $out;
444 1 50       8 if (blessed $ex) {
    0          
445 1         4 my $newclass = ref $ex;
446 1 50       6 bless $ex, $class if $class;
447 1 50 33     7 if ($OPTIONS{dump} && !overload::OverloadedStringify($ex)) {
448 0         0 $out = _ref_formatter($ex);
449             }
450             else {
451 1         43 $out = "$ex";
452             }
453 1 50       12 bless $ex, $newclass if $class;
454             }
455             elsif ($OPTIONS{dump}) {
456 0         0 $out = _ref_formatter($ex);
457             }
458             else {
459 0         0 $out = "$ex";
460             }
461 1         9 return ($out, $message);
462             }
463              
464             {
465             package #hide
466             Devel::Confess::_Attached;
467             use overload
468             fallback => 1,
469             'bool' => sub {
470             package
471             Devel::Confess;
472 0     0   0 my $ex = $_[0];
473 0         0 my $class = $PACKAGES{refaddr($ex)};
474 0         0 my $newclass = ref $ex;
475 0         0 bless $ex, $class;
476 0 0       0 my $out = $ex ? !!1 : !!0;
477 0         0 bless $ex, $newclass;
478 0         0 return $out;
479             },
480             '0+' => sub {
481             package
482             Devel::Confess;
483 0     0   0 my $ex = $_[0];
484 0         0 my $class = $PACKAGES{refaddr($ex)};
485 0         0 my $newclass = ref $ex;
486 0         0 bless $ex, $class;
487 0         0 my $out = 0+sprintf '%.20g', $ex;
488 0         0 bless $ex, $newclass;
489 0         0 return $out;
490             },
491             '""' => sub {
492             package
493             Devel::Confess;
494 1     1   61 join('', _ex_as_strings(@_));
495             },
496 9     9   10808 ;
  9         7908  
  9         1391  
497              
498             sub DESTROY {
499             package
500             Devel::Confess;
501 2     2   1237 my $ex = $_[0];
502 2         8 my $id = refaddr($ex);
503 2 50       9 my $class = delete $PACKAGES{$id} or return;
504 2         6 delete $MESSAGES{$id};
505 2         5 delete $EXCEPTIONS{$id};
506              
507 2         18 my $newclass = ref $ex;
508              
509 2         4 my $cloned;
510             # delete_package is more complete, but can explode on some perls
511 2         8 if (_BROKEN_CLONED_GLOB_UNDEF && delete $CLONED{$id}) {
512             $cloned = 1;
513 9     9   1637 no strict 'refs';
  9         13  
  9         1822  
514             @{"${newclass}::ISA"} = ();
515             my $stash = \%{"${newclass}::"};
516             delete @{$stash}{keys %$stash};
517             }
518             else {
519 2         12 Symbol::delete_package($newclass);
520             }
521              
522 2 50       172 if (_BROKEN_CLONED_DESTROY_REBLESS && $cloned || delete $CLONED{$id}) {
523 0   0     0 my $destroy = _can($class, 'DESTROY') || return;
524 0         0 goto $destroy;
525             }
526              
527 2         9 bless $ex, $class;
528              
529             # after reblessing, perl will re-dispatch to the class's own DESTROY.
530 2         12 ();
531             }
532             }
533              
534             1;
535             __END__