File Coverage

blib/lib/Devel/Confess.pm
Criterion Covered Total %
statement 215 278 77.3
branch 98 156 62.8
condition 35 76 46.0
subroutine 32 37 86.4
pod n/a
total 380 547 69.4


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