File Coverage

blib/lib/Trace/Mask/Reference.pm
Criterion Covered Total %
statement 115 115 100.0
branch 56 62 90.3
condition 37 51 72.5
subroutine 15 15 100.0
pod 4 5 80.0
total 227 248 91.5


line stmt bran cond sub pod time code
1             package Trace::Mask::Reference;
2 4     4   384575 use strict;
  4         9  
  4         108  
3 4     4   22 use warnings;
  4         6  
  4         111  
4              
5 4     4   21 use Carp qw/croak/;
  4         8  
  4         207  
6              
7 4     4   27 use Scalar::Util qw/reftype looks_like_number refaddr blessed/;
  4         9  
  4         266  
8              
9 4     4   1084 use Trace::Mask::Util qw/mask_frame mask_line get_mask/;
  4         8  
  4         309  
10              
11 4     4   22 use base 'Exporter';
  4         8  
  4         511  
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 3077 my $code = shift;
21 3         6 local $@;
22 3         6 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   24 BEGIN { mask_line({hide => 3}, 1) }
28 3         9 $code->();
29 2         5 1;
30             };
31 3 100       20 return if $ok;
32 1   50     8 return $@ || "error was smashed!";
33             }
34              
35             sub _call_details {
36 8304     8304   14812 my ($level) = @_;
37 8304         9753 $level += 1;
38              
39 8304         9070 my (@call, @args);
40             {
41 8304         9493 package DB;
42 8304         53207 @call = caller($level);
43 8304         19270 @args = @DB::args;
44             }
45              
46 8304 100 66     33770 return unless @call && defined $call[0];
47 8093         24099 return (\@call, \@args);
48             }
49              
50             sub _do_shift {
51 79     79   2219 my ($shift, $frame) = @_;
52              
53             # Args are a direct move
54 79         148 $frame->[1] = $shift->[1];
55              
56             # Merge the masks numeric keys, shift wins
57 79         119 for my $key (keys %{$shift->[2]}) {
  79         243  
58 198 100       663 next unless $key =~ m/^\d+$/;
59 119         233 $frame->[2]->{$key} = $shift->[2]->{$key};
60             }
61              
62             # Copy all caller values from shift except 0-2
63 79         177 for(my $i = 3; $i < @{$shift->[0]}; $i++) {
  707         1652  
64 628         1079 $frame->[0]->[$i] = $shift->[0]->[$i];
65             }
66             }
67              
68             sub trace {
69 210     210 1 22700 my @stack;
70              
71             # Always have to start at 0 since frames can hide frames that come after them.
72 210         328 my $level = 0;
73              
74             # Shortcut
75 210 100       568 if ($ENV{NO_TRACE_MASK}) {
76 14         36 while (my ($call, $args) = _call_details($level++)) {
77 287         919 push @stack => [$call, $args];
78             }
79 14         96 return \@stack;
80             }
81              
82 196         252 my ($shift, $last);
83 196         226 my $skip = 0;
84 196         222 my $stopped = 0;
85 196         483 while (my ($call, $args) = _call_details($level++)) {
86 7805         9252 my $mask = get_mask(@{$call}[1,2,3]);
  7805         21276  
87 7805         16413 my $frame = [$call, $args, $mask];
88              
89 7805         11982 my $lock = $mask->{lock};
90              
91 7805 100 100     57636 next if $stopped && !($mask->{restart} || $lock);
      100        
92 1573 100       3249 $stopped = 0 if $mask->{restart};
93              
94 1573 100 66     7465 $last = $frame unless $mask->{hide} || $mask->{shift} || $lock;
      100        
95              
96 1573 100       3475 unless($lock) {
97             # Need to do this even if the frame is not pushed now, it may be pushed
98             # later depending on shift.
99 1539         3680 for my $idx (keys %$mask) {
100 984 100       3148 next unless $idx =~ m/^\d+$/;
101 325 100       794 next if $idx >= @$call; # Do not create new call indexes
102 312         657 $call->[$idx] = $mask->{$idx};
103             }
104             }
105              
106 1573 100 100     7350 if ($mask->{shift}) {
    100 100        
    100          
107 78   33     318 $shift ||= $frame;
108 78 50 33     329 $skip = ($skip || $lock) ? $skip + $mask->{shift} - 1 : $mask->{shift};
109             }
110             elsif ($mask->{hide}) {
111 213 100 100     859 $skip = ($skip || $lock) ? $skip + $mask->{hide} - 1 : $mask->{hide};
112             }
113             elsif($skip && !(--$skip) && $shift) {
114 65 50       217 _do_shift($shift, $frame) unless $lock;
115 65         94 $shift = undef;
116             }
117              
118 1573   66     5369 my $push = !($skip || ($mask->{no_start} && !@stack));
119              
120 1573 100 66     5002 push @stack => $frame if $push || $lock;
121              
122 1573 100       6310 $stopped = 1 if $mask->{stop};
123             }
124              
125 196 100       512 if ($shift) {
126 13 50       65 _do_shift($shift, $last) unless $last->[2]->{lock};
127 13 50 33     87 push @stack => $last unless @stack && $stack[-1] == $last;
128             }
129              
130 196         3995 return \@stack;
131             }
132              
133             sub trace_mask_caller {
134 15     15 1 3490 my ($level) = @_;
135 15 100       37 $level = 0 unless defined($level);
136              
137 15         28 my $trace = trace();
138 15 50 33     70 return unless $trace && @$trace;
139              
140 15         22 my $frame = $trace->[$level + 2];
141 15 50       31 return unless $frame;
142              
143 15 100       33 return @{$frame->[0]}[0, 1, 2] unless @_;
  1         8  
144 14         15 return @{$frame->[0]};
  14         478  
145             }
146              
147             sub trace_string {
148 3     3 1 22 my ($level) = @_;
149 3 100       9 $level = 0 unless defined($level);
150              
151 4     4   26 BEGIN { mask_line({hide => 1}, 1) };
152 3         9 my $trace = trace();
153              
154 3   66     28 shift @$trace while @$trace && $level--;
155 3         6 my $string = "";
156 3         6 for my $frame (@$trace) {
157 18         29 my ($call, $args) = @$frame;
158 18         31 my $args_str = join ", " => map { render_arg($_) } @$args;
  13         26  
159 18   100     46 $args_str ||= '';
160 18 100       33 if ($call->[3] eq '(eval)') {
161 1         5 $string .= "eval { ... } called at $call->[1] line $call->[2]\n";
162             }
163             else {
164 17         66 $string .= "$call->[3]($args_str) called at $call->[1] line $call->[2]\n";
165             }
166             }
167              
168 3         41 return $string;
169             }
170              
171             sub render_arg {
172 19     19 0 1496 my $arg = shift;
173 19 100       49 return 'undef' unless defined($arg);
174              
175 18 100       38 if (ref($arg)) {
176 2         7 my $type = reftype($arg);
177              
178             # Look past overloading
179 2   100     12 my $class = blessed($arg) || '';
180 2         11 my $it = sprintf('0x%x', refaddr($arg));
181 2         5 my $ref = "$type($it)";
182              
183 2 100       14 return $ref unless $class;
184 1         7 return "$class=$ref";
185             }
186              
187 16 100       54 return $arg if looks_like_number($arg);
188 14         18 $arg =~ s/'/\\'/g;
189 14         42 return "'$arg'";
190             }
191              
192             1;
193              
194             __END__