File Coverage

blib/lib/Trace/Mask/Reference.pm
Criterion Covered Total %
statement 118 118 100.0
branch 60 66 90.9
condition 40 54 74.0
subroutine 15 15 100.0
pod 4 5 80.0
total 237 258 91.8


line stmt bran cond sub pod time code
1             package Trace::Mask::Reference;
2 4     4   226962 use strict;
  4         6  
  4         90  
3 4     4   13 use warnings;
  4         5  
  4         89  
4              
5 4     4   12 use Carp qw/croak/;
  4         5  
  4         147  
6              
7 4     4   14 use Scalar::Util qw/reftype looks_like_number refaddr blessed/;
  4         5  
  4         191  
8              
9 4     4   630 use Trace::Mask::Util qw/mask_frame mask_line get_mask/;
  4         6  
  4         197  
10              
11 4     4   17 use base 'Exporter';
  4         5  
  4         368  
12             our @EXPORT_OK = qw{
13             trace
14             trace_string
15             trace_mask_caller
16             try_example
17             };
18              
19             sub try_example(&) {
20 3     3 1 1365 my $code = shift;
21 3         3 local $@;
22 3         4 my $ok = eval {
23             # Hides the call, the eval, and the call to try_example
24             # This also has the added benefit that if there was an exception inside
25             # try_example itself, the trace would not hide anything. The hide only
26             # effects traces from inside the anonymous sub.
27 4     4   18 BEGIN { mask_line({hide => 3}, 1) }
28 3         5 $code->();
29 2         5 1;
30             };
31 3 100       17 return if $ok;
32 1   50     6 return $@ || "error was smashed!";
33             }
34              
35             sub _call_details {
36 4074     4074   4991 my ($level) = @_;
37 4074         2701 $level += 1;
38              
39 4074         2284 my (@call, @args);
40             {
41 4074         2370 package DB;
42 4074         16611 @call = caller($level);
43 4074         5455 @args = @DB::args;
44             }
45              
46 4074 100 66     10828 return unless @call && defined $call[0];
47 3863         6829 return (\@call, \@args);
48             }
49              
50             sub _do_shift {
51 79     79   1135 my ($shift, $frame) = @_;
52              
53             # Args are a direct move
54 79         112 $frame->[1] = $shift->[1];
55              
56             # Merge the masks numeric keys, shift wins
57 79         71 for my $key (keys %{$shift->[2]}) {
  79         175  
58 198 100       415 next unless $key =~ m/^\d+$/;
59 119         133 $frame->[2]->{$key} = $shift->[2]->{$key};
60             }
61              
62             # Copy all caller values from shift except 0-2
63 79         130 for(my $i = 3; $i < @{$shift->[0]}; $i++) {
  707         975  
64 628         566 $frame->[0]->[$i] = $shift->[0]->[$i];
65             }
66             }
67              
68             sub trace {
69 210     210 1 12687 my @stack;
70              
71             # Always have to start at 0 since frames can hide frames that come after them.
72 210         179 my $level = 0;
73              
74             # Shortcut
75 210 100       353 if ($ENV{NO_TRACE_MASK}) {
76 14         24 while (my ($call, $args) = _call_details($level++)) {
77 231         418 push @stack => [$call, $args];
78             }
79 14         67 return \@stack;
80             }
81              
82 196         144 my ($shift, $last);
83 196         144 my $skip = 0;
84 196         120 my $stopped = 0;
85 196         132 my $paused = 0;
86 196         295 while (my ($call, $args) = _call_details($level++)) {
87 3631         2374 my $mask = get_mask(@{$call}[1,2,3]);
  3631         6275  
88 3631         3804 my $frame = [$call, $args, $mask];
89              
90 3631         3057 my $lock = $mask->{lock};
91              
92 3631 100 100     7855 next if $paused && !($mask->{restart} || $lock);
      100        
93 2959 100       3474 $paused = 0 if $mask->{restart};
94              
95 2959 100 100     7850 next if $stopped && !$lock;
96              
97 1573 100 66     4895 $last = $frame unless $mask->{hide} || $mask->{shift} || $lock;
      100        
98              
99 1573 100       2013 unless($lock) {
100             # Need to do this even if the frame is not pushed now, it may be pushed
101             # later depending on shift.
102 1539         2368 for my $idx (keys %$mask) {
103 984 100       2054 next unless $idx =~ m/^\d+$/;
104 325 100       522 next if $idx >= @$call; # Do not create new call indexes
105 312         383 $call->[$idx] = $mask->{$idx};
106             }
107             }
108              
109 1573 100 100     4873 if ($mask->{shift}) {
    100 100        
    100          
110 78   33     220 $shift ||= $frame;
111 78 50 33     243 $skip = ($skip || $lock) ? $skip + $mask->{shift} - 1 : $mask->{shift};
112             }
113             elsif ($mask->{hide}) {
114 213 100 100     611 $skip = ($skip || $lock) ? $skip + $mask->{hide} - 1 : $mask->{hide};
115             }
116             elsif($skip && !(--$skip) && $shift) {
117 65 50       163 _do_shift($shift, $frame) unless $lock;
118 65         61 $shift = undef;
119             }
120              
121 1573   66     3152 my $push = !($skip || ($mask->{no_start} && !@stack));
122              
123 1573 100 66     3077 push @stack => $frame if $push || $lock;
124              
125 1573 100       1902 $stopped = 1 if $mask->{stop};
126 1573 100       3769 $paused = 1 if $mask->{pause};
127             }
128              
129 196 100       299 if ($shift) {
130 13 50       42 _do_shift($shift, $last) unless $last->[2]->{lock};
131 13 50 33     71 push @stack => $last unless @stack && $stack[-1] == $last;
132             }
133              
134 196         2694 return \@stack;
135             }
136              
137             sub trace_mask_caller {
138 15     15 1 2272 my ($level) = @_;
139 15 100       25 $level = 0 unless defined($level);
140              
141 15         20 my $trace = trace();
142 15 50 33     51 return unless $trace && @$trace;
143              
144 15         17 my $frame = $trace->[$level + 2];
145 15 50       23 return unless $frame;
146              
147 15 100       24 return @{$frame->[0]}[0, 1, 2] unless @_;
  1         6  
148 14         9 return @{$frame->[0]};
  14         320  
149             }
150              
151             sub trace_string {
152 3     3 1 16 my ($level) = @_;
153 3 100       8 $level = 0 unless defined($level);
154              
155 4     4   15 BEGIN { mask_line({hide => 1}, 1) };
156 3         6 my $trace = trace();
157              
158 3   66     21 shift @$trace while @$trace && $level--;
159 3         4 my $string = "";
160 3         5 for my $frame (@$trace) {
161 18         20 my ($call, $args) = @$frame;
162 18         18 my $args_str = join ", " => map { render_arg($_) } @$args;
  13         11  
163 18   100     29 $args_str ||= '';
164 18 100       24 if ($call->[3] eq '(eval)') {
165 1         4 $string .= "eval { ... } called at $call->[1] line $call->[2]\n";
166             }
167             else {
168 17         39 $string .= "$call->[3]($args_str) called at $call->[1] line $call->[2]\n";
169             }
170             }
171              
172 3         25 return $string;
173             }
174              
175             sub render_arg {
176 19     19 0 1184 my $arg = shift;
177 19 100       30 return 'undef' unless defined($arg);
178              
179 18 100       28 if (ref($arg)) {
180 2         5 my $type = reftype($arg);
181              
182             # Look past overloading
183 2   100     9 my $class = blessed($arg) || '';
184 2         7 my $it = sprintf('0x%x', refaddr($arg));
185 2         8 my $ref = "$type($it)";
186              
187 2 100       11 return $ref unless $class;
188 1         5 return "$class=$ref";
189             }
190              
191 16 100       36 return $arg if looks_like_number($arg);
192 14         11 $arg =~ s/'/\\'/g;
193 14         25 return "'$arg'";
194             }
195              
196             1;
197              
198             __END__