File Coverage

blib/lib/Devel/StackTrace.pm
Criterion Covered Total %
statement 143 145 98.6
branch 51 60 85.0
condition 15 20 75.0
subroutine 22 22 100.0
pod 9 9 100.0
total 240 256 93.7


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