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   383075 use strict;
  4         8  
  4         98  
3 4     4   18 use warnings;
  4         8  
  4         98  
4              
5 4     4   18 use Carp qw/croak/;
  4         8  
  4         229  
6              
7 4     4   19 use Scalar::Util qw/reftype looks_like_number refaddr blessed/;
  4         6  
  4         265  
8              
9 4     4   1093 use Trace::Mask::Util qw/mask_frame mask_line get_mask/;
  4         10  
  4         241  
10              
11 4     4   21 use base 'Exporter';
  4         7  
  4         460  
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 3098 my $code = shift;
21 3         6 local $@;
22 3         5 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   21 BEGIN { mask_line({hide => 3}, 1) }
28 3         8 $code->();
29 2         6 1;
30             };
31 3 100       19 return if $ok;
32 1   50     9 return $@ || "error was smashed!";
33             }
34              
35             sub _call_details {
36 8694     8694   15916 my ($level) = @_;
37 8694         11932 $level += 1;
38              
39 8694         10983 my (@call, @args);
40             {
41 8694         10671 package DB;
42 8694         58706 @call = caller($level);
43 8694         22902 @args = @DB::args;
44             }
45              
46 8694 100 66     39139 return unless @call && defined $call[0];
47 8483         28226 return (\@call, \@args);
48             }
49              
50             sub _do_shift {
51 79     79   2343 my ($shift, $frame) = @_;
52              
53             # Args are a direct move
54 79         155 $frame->[1] = $shift->[1];
55              
56             # Merge the masks numeric keys, shift wins
57 79         122 for my $key (keys %{$shift->[2]}) {
  79         248  
58 198 100       644 next unless $key =~ m/^\d+$/;
59 119         267 $frame->[2]->{$key} = $shift->[2]->{$key};
60             }
61              
62             # Copy all caller values from shift except 0-2
63 79         193 for(my $i = 3; $i < @{$shift->[0]}; $i++) {
  707         1687  
64 628         1103 $frame->[0]->[$i] = $shift->[0]->[$i];
65             }
66             }
67              
68             sub trace {
69 210     210 1 21247 my @stack;
70              
71             # Always have to start at 0 since frames can hide frames that come after them.
72 210         329 my $level = 0;
73              
74             # Shortcut
75 210 100       606 if ($ENV{NO_TRACE_MASK}) {
76 14         49 while (my ($call, $args) = _call_details($level++)) {
77 287         1246 push @stack => [$call, $args];
78             }
79 14         110 return \@stack;
80             }
81              
82 196         273 my ($shift, $last);
83 196         269 my $skip = 0;
84 196         261 my $stopped = 0;
85 196         541 while (my ($call, $args) = _call_details($level++)) {
86 8195         10511 my $mask = get_mask(@{$call}[1,2,3]);
  8195         24659  
87 8195         18986 my $frame = [$call, $args, $mask];
88              
89 8195         13142 my $lock = $mask->{lock};
90              
91 8195 100 100     64567 next if $stopped && !($mask->{restart} || $lock);
      100        
92 1703 100       3586 $stopped = 0 if $mask->{restart};
93              
94 1703 100 66     8727 $last = $frame unless $mask->{hide} || $mask->{shift} || $lock;
      100        
95              
96 1703 100       3826 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 1669         4149 for my $idx (keys %$mask) {
100 1114 100       3766 next unless $idx =~ m/^\d+$/;
101 325 100       864 next if $idx >= @$call; # Do not create new call indexes
102 312         655 $call->[$idx] = $mask->{$idx};
103             }
104             }
105              
106 1703 100 100     8546 if ($mask->{shift}) {
    100 100        
    100          
107 78   33     374 $shift ||= $frame;
108 78 50 33     403 $skip = ($skip || $lock) ? $skip + $mask->{shift} - 1 : $mask->{shift};
109             }
110             elsif ($mask->{hide}) {
111 213 100 100     990 $skip = ($skip || $lock) ? $skip + $mask->{hide} - 1 : $mask->{hide};
112             }
113             elsif($skip && !(--$skip) && $shift) {
114 65 50       230 _do_shift($shift, $frame) unless $lock;
115 65         119 $shift = undef;
116             }
117              
118 1703   66     6005 my $push = !($skip || ($mask->{no_start} && !@stack));
119              
120 1703 100 66     5729 push @stack => $frame if $push || $lock;
121              
122 1703 100       7103 $stopped = 1 if $mask->{stop};
123             }
124              
125 196 100       501 if ($shift) {
126 13 50       65 _do_shift($shift, $last) unless $last->[2]->{lock};
127 13 50 33     92 push @stack => $last unless @stack && $stack[-1] == $last;
128             }
129              
130 196         3987 return \@stack;
131             }
132              
133             sub trace_mask_caller {
134 15     15 1 3107 my ($level) = @_;
135 15 100       36 $level = 0 unless defined($level);
136              
137 15         28 my $trace = trace();
138 15 50 33     74 return unless $trace && @$trace;
139              
140 15         22 my $frame = $trace->[$level + 2];
141 15 50       29 return unless $frame;
142              
143 15 100       33 return @{$frame->[0]}[0, 1, 2] unless @_;
  1         9  
144 14         17 return @{$frame->[0]};
  14         588  
145             }
146              
147             sub trace_string {
148 3     3 1 21 my ($level) = @_;
149 3 100       11 $level = 0 unless defined($level);
150              
151 4     4   41 BEGIN { mask_line({hide => 1}, 1) };
152 3         7 my $trace = trace();
153              
154 3   66     28 shift @$trace while @$trace && $level--;
155 3         5 my $string = "";
156 3         6 for my $frame (@$trace) {
157 18         40 my ($call, $args) = @$frame;
158 18         37 my $args_str = join ", " => map { render_arg($_) } @$args;
  13         22  
159 18   100     49 $args_str ||= '';
160 18 100       36 if ($call->[3] eq '(eval)') {
161 1         6 $string .= "eval { ... } called at $call->[1] line $call->[2]\n";
162             }
163             else {
164 17         70 $string .= "$call->[3]($args_str) called at $call->[1] line $call->[2]\n";
165             }
166             }
167              
168 3         43 return $string;
169             }
170              
171             sub render_arg {
172 19     19 0 1534 my $arg = shift;
173 19 100       50 return 'undef' unless defined($arg);
174              
175 18 100       37 if (ref($arg)) {
176 2         6 my $type = reftype($arg);
177              
178             # Look past overloading
179 2   100     13 my $class = blessed($arg) || '';
180 2         10 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       72 return $arg if looks_like_number($arg);
188 14         21 $arg =~ s/'/\\'/g;
189 14         41 return "'$arg'";
190             }
191              
192             1;
193              
194             __END__