File Coverage

blib/lib/Devel/StackTrace.pm
Criterion Covered Total %
statement 144 146 98.6
branch 51 60 85.0
condition 15 20 75.0
subroutine 23 23 100.0
pod 9 9 100.0
total 242 258 93.8


line stmt bran cond sub pod time code
1             package Devel::StackTrace;
2              
3 10     10   1301804 use 5.006;
  10         48  
4              
5 10     10   55 use strict;
  10         22  
  10         381  
6 10     10   48 use warnings;
  10         20  
  10         879  
7              
8             our $VERSION = '2.05';
9              
10 10     10   9706 use Devel::StackTrace::Frame;
  10         35  
  10         452  
11 10     10   75 use File::Spec;
  10         41  
  10         311  
12 10     10   55 use Scalar::Util qw( blessed );
  10         18  
  10         964  
13              
14             use overload
15             '""' => \&as_string,
16 1     1   10 bool => sub {1},
17 10     10   6731 fallback => 1;
  10         19214  
  10         110  
18              
19             sub new {
20 37     37 1 4210345 my $class = shift;
21 37         180 my %p = @_;
22              
23             $p{unsafe_ref_capture} = !delete $p{no_refs}
24 37 100       153 if exists $p{no_refs};
25              
26 37         210 my $self = bless {
27             index => undef,
28             frames => [],
29             raw => [],
30             %p,
31             }, $class;
32              
33 37         162 $self->_record_caller_data;
34              
35 37         182 return $self;
36             }
37              
38             sub _record_caller_data {
39 37     37   106 my $self = shift;
40              
41 37   66     404 my $filter = $self->{filter_frames_early} && $self->_make_frame_filter;
42              
43             # We exclude this method by starting at least one frame back.
44 37   100     170 my $x = 1 + ( $self->{skip_frames} || 0 );
45              
46 37 100       111 while (
47             my @c
48             = $self->{no_args}
49             ? caller( $x++ )
50             : do {
51             ## no critic (Modules::ProhibitMultiplePackages, Variables::ProhibitPackageVars)
52             package # the newline keeps dzil from adding a version here
53             DB;
54 119         211 @DB::args = ();
55 119         908 caller( $x++ );
56             }
57             ) {
58              
59 86         141 my @args;
60              
61             ## no critic (Variables::ProhibitPackageVars, BuiltinFunctions::ProhibitComplexMappings)
62 86 100       221 unless ( $self->{no_args} ) {
63              
64             # This is the same workaroud as was applied to Carp.pm a little
65             # while back
66             # (https://rt.perl.org/Public/Bug/Display.html?id=131046):
67             #
68             # Guard our serialization of the stack from stack refcounting
69             # bugs NOTE this is NOT a complete solution, we cannot 100%
70             # guard against these bugs. However in many cases Perl *is*
71             # capable of detecting them and throws an error when it
72             # does. Unfortunately serializing the arguments on the stack is
73             # a perfect way of finding these bugs, even when they would not
74             # affect normal program flow that did not poke around inside the
75             # stack. Inside of Carp.pm it makes little sense reporting these
76             # bugs, as Carp's job is to report the callers errors, not the
77             # ones it might happen to tickle while doing so. See:
78             # https://rt.perl.org/Public/Bug/Display.html?id=131046 and:
79             # https://rt.perl.org/Public/Bug/Display.html?id=52610 for more
80             # details and discussion. - Yves
81             @args = map {
82 83         154 my $arg;
  118         168  
83 118         204 local $@ = $@;
84             eval {
85 118         168 $arg = $_;
86 118         247 1;
87 118 50       185 } or do {
88 0         0 $arg = '** argument not available anymore **';
89             };
90 118         334 $arg;
91             } @DB::args;
92             }
93             ## use critic
94              
95 86         294 my $raw = {
96             caller => \@c,
97             args => \@args,
98             };
99              
100 86 50 66     222 next if $filter && !$filter->($raw);
101              
102 86 100       1066 unless ( $self->{unsafe_ref_capture} ) {
103 108 100       323 $raw->{args} = [ map { ref $_ ? $self->_ref_to_string($_) : $_ }
104 80         122 @{ $raw->{args} } ];
  80         159  
105             }
106              
107 86         169 push @{ $self->{raw} }, $raw;
  86         335  
108             }
109             }
110              
111             sub _ref_to_string {
112 12     12   25 my $self = shift;
113 12         24 my $ref = shift;
114              
115 12 50 66     126 return overload::AddrRef($ref)
116             if blessed $ref && $ref->isa('Exception::Class::Base');
117              
118 12 100       65 return overload::AddrRef($ref) unless $self->{respect_overload};
119              
120             ## no critic (Variables::RequireInitializationForLocalVars)
121 2         5 local $@;
122 2         9 local $SIG{__DIE__};
123             ## use critic
124              
125 2         6 my $str = eval { $ref . q{} };
  2         46  
126              
127 2 100       21 return $@ ? overload::AddrRef($ref) : $str;
128             }
129              
130             sub _make_frames {
131 34     34   56 my $self = shift;
132              
133 34   66     159 my $filter = !$self->{filter_frames_early} && $self->_make_frame_filter;
134              
135 34         88 my $raw = delete $self->{raw};
136 34         61 for my $r ( @{$raw} ) {
  34         104  
137 80 100 100     214 next if $filter && !$filter->($r);
138              
139 73         231 $self->_add_frame( $r->{caller}, $r->{args} );
140             }
141             }
142              
143             my $default_filter = sub {1};
144              
145             sub _make_frame_filter {
146 34     34   56 my $self = shift;
147              
148 34         62 my ( @i_pack_re, %i_class );
149 34 100       99 if ( $self->{ignore_package} ) {
150             ## no critic (Variables::RequireInitializationForLocalVars)
151 2         20 local $@;
152 2         13 local $SIG{__DIE__};
153             ## use critic
154              
155             $self->{ignore_package} = [ $self->{ignore_package} ]
156 2 50       6 unless eval { @{ $self->{ignore_package} } };
  2         6  
  2         33  
157              
158             @i_pack_re
159 2 100       6 = map { ref $_ ? $_ : qr/^\Q$_\E$/ } @{ $self->{ignore_package} };
  2         40  
  2         6  
160             }
161              
162 34         58 my $p = __PACKAGE__;
163 34         429 push @i_pack_re, qr/^\Q$p\E$/;
164              
165 34 100       108 if ( $self->{ignore_class} ) {
166             $self->{ignore_class} = [ $self->{ignore_class} ]
167 2 50       6 unless ref $self->{ignore_class};
168 2         3 %i_class = map { $_ => 1 } @{ $self->{ignore_class} };
  2         5  
  2         4  
169             }
170              
171 34         71 my $user_filter = $self->{frame_filter};
172              
173             return sub {
174 80 100   80   139 return 0 if grep { $_[0]{caller}[0] =~ /$_/ } @i_pack_re;
  84         395  
175 78 100       221 return 0 if grep { $_[0]{caller}[0]->isa($_) } keys %i_class;
  6         42  
176              
177 74 100       141 if ($user_filter) {
178 5         18 return $user_filter->( $_[0] );
179             }
180              
181 69         215 return 1;
182 34         257 };
183             }
184              
185             sub _add_frame {
186 73     73   142 my $self = shift;
187 73         124 my $c = shift;
188 73         124 my $p = shift;
189              
190             # eval and is_require are only returned when applicable under 5.00503.
191 73 50       156 push @$c, ( undef, undef ) if scalar @$c == 6;
192              
193 73         528 push @{ $self->{frames} },
194             Devel::StackTrace::Frame->new(
195             $c,
196             $p,
197             $self->{respect_overload},
198             $self->{max_arg_length},
199             $self->{message},
200             $self->{indent}
201 73         122 );
202             }
203              
204             sub next_frame {
205 13     13 1 991 my $self = shift;
206              
207             # reset to top if necessary.
208 13 100       30 $self->{index} = -1 unless defined $self->{index};
209              
210 13         30 my @f = $self->frames;
211 13 100       30 if ( defined $f[ $self->{index} + 1 ] ) {
212 10         24 return $f[ ++$self->{index} ];
213             }
214             else {
215 3         5 $self->{index} = undef;
216             ## no critic (Subroutines::ProhibitExplicitReturnUndef)
217 3         7 return undef;
218             }
219             }
220              
221             sub prev_frame {
222 9     9 1 35 my $self = shift;
223              
224 9         16 my @f = $self->frames;
225              
226             # reset to top if necessary.
227 9 100       16 $self->{index} = scalar @f unless defined $self->{index};
228              
229 9 100 66     25 if ( defined $f[ $self->{index} - 1 ] && $self->{index} >= 1 ) {
230 6         14 return $f[ --$self->{index} ];
231             }
232             else {
233             ## no critic (Subroutines::ProhibitExplicitReturnUndef)
234 3         4 $self->{index} = undef;
235 3         6 return undef;
236             }
237             }
238              
239             sub reset_pointer {
240 1     1 1 1 my $self = shift;
241              
242 1         2 $self->{index} = undef;
243              
244 1         3 return;
245             }
246              
247             sub frames {
248 59     59 1 152 my $self = shift;
249              
250 59 100       111 if (@_) {
251             die
252             "Devel::StackTrace->frames can only take Devel::StackTrace::Frame args\n"
253 2 50       3 if grep { !$_->isa('Devel::StackTrace::Frame') } @_;
  3         23  
254              
255 2         6 $self->{frames} = \@_;
256 2         8 delete $self->{raw};
257             }
258             else {
259 57 100       192 $self->_make_frames if $self->{raw};
260             }
261              
262 59         195 return @{ $self->{frames} };
  59         175  
263             }
264              
265             sub frame {
266 2     2 1 4 my $self = shift;
267 2         4 my $i = shift;
268              
269 2 50       7 return unless defined $i;
270              
271 2         10 return ( $self->frames )[$i];
272             }
273              
274             sub frame_count {
275 1     1 1 4 my $self = shift;
276              
277 1         3 return scalar( $self->frames );
278             }
279              
280 3     3 1 9 sub message { $_[0]->{message} }
281              
282             sub as_string {
283 18     18 1 121 my $self = shift;
284 18         37 my $p = shift;
285              
286 18         53 my @frames = $self->frames;
287 18 100       50 if (@frames) {
288 15         24 my $st = q{};
289 15         25 my $first = 1;
290 15         29 for my $f (@frames) {
291 36         94 $st .= $f->as_string( $first, $p ) . "\n";
292 36         74 $first = 0;
293             }
294              
295 15         103 return $st;
296             }
297              
298 3         8 my $msg = $self->message;
299 3 50       21 return $msg if defined $msg;
300              
301 0           return 'Trace begun';
302             }
303              
304             {
305             ## no critic (Modules::ProhibitMultiplePackages, ClassHierarchies::ProhibitExplicitISA)
306             package # hide from PAUSE
307             Devel::StackTraceFrame;
308              
309             our @ISA = 'Devel::StackTrace::Frame';
310             }
311              
312             1;
313              
314             # ABSTRACT: An object representing a stack trace
315              
316             __END__