File Coverage

blib/lib/Devel/MAT/Tool/Reachability.pm
Criterion Covered Total %
statement 97 117 82.9
branch 41 60 68.3
condition 33 66 50.0
subroutine 8 10 80.0
pod 0 3 0.0
total 179 256 69.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2013-2018 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT::Tool::Reachability 0.49;
7              
8 5     5   3250 use v5.14;
  5         18  
9 5     5   23 use warnings;
  5         10  
  5         131  
10              
11 5     5   22 use constant FOR_UI => 1;
  5         10  
  5         225  
12              
13 5     5   35 use List::Util qw( pairvalues );
  5         16  
  5         340  
14              
15             =head1 NAME
16              
17             C - analyse how SVs are reachable
18              
19             =head1 DESCRIPTION
20              
21             This C tool determines which SVs are reachable via any known roots
22             and which are not. For reachable SVs, they are classified into several broad
23             categories:
24              
25             =over 2
26              
27             =item *
28              
29             SVs that directly make up the symbol table.
30              
31             =item *
32              
33             SVs that form the padlist of functions or store the names of lexical
34             variables.
35              
36             =item *
37              
38             SVs that hold the value of lexical variables.
39              
40             =item *
41              
42             User data stored in package globals, lexical variables, or referenced
43             recursively via structures stored in them.
44              
45             =item *
46              
47             Miscellaneous other SVs that are used to implement the internals of the
48             interpreter.
49              
50             =back
51              
52             =cut
53              
54             use constant {
55 5         8373 REACH_SYMTAB => 1,
56             REACH_USER => 2,
57             REACH_PADLIST => 3,
58             REACH_LEXICAL => 4,
59             REACH_INTERNAL => 5,
60 5     5   28 };
  5         9  
61              
62             sub new
63             {
64 1     1 0 2 my $class = shift;
65 1         3 my ( $pmat, %args ) = @_;
66              
67             *Devel::MAT::SV::reachable = sub {
68 2     2   21 my $sv = shift;
69 2         10 return $sv->{tool_reachable};
70 1         32 };
71              
72 1         5 $class->mark_reachable( $pmat->dumpfile, progress => $args{progress} );
73              
74 1         10 return $class;
75             }
76              
77             my @ICONS = (
78             "none", "symtab", "user", "padlist", "lexical", "internal"
79             );
80             sub _reach2icon
81             {
82 0     0   0 my ( $sv ) = @_;
83 0   0     0 my $reach = $sv->{tool_reachable} // 0;
84              
85 0   0     0 my $icon = $ICONS[$reach] // die "Unknown reachability value $reach";
86 0         0 return "reachable-$icon";
87             }
88              
89             sub init_ui
90             {
91 0     0 0 0 my $self = shift;
92 0         0 my ( $ui ) = @_;
93              
94 0         0 foreach ( @ICONS ) {
95 0         0 $ui->register_icon(
96             name => "reachable-$_",
97             svg => "icons/reachable-$_.svg"
98             );
99             }
100              
101 0         0 my $column = $ui->provides_svlist_column(
102             title => "R",
103             type => "icon",
104             );
105              
106 0         0 $ui->provides_sv_detail(
107             title => "Reachable",
108             type => "icon",
109             render => \&_reach2icon,
110             );
111              
112 0         0 $ui->set_svlist_column_values(
113             column => $column,
114             from => \&_reach2icon,
115             );
116             }
117              
118             sub mark_reachable
119             {
120 1     1 0 1 my $self = shift;
121 1         4 my ( $df, %args ) = @_;
122              
123 1         2 my $progress = $args{progress};
124              
125 1         1 my @user;
126             my @internal;
127              
128             # First, walk the symbol table
129             {
130 1         4 my @symtab = ( $df->defstash );
131 1         3 $symtab[0]->{tool_reachable} = REACH_SYMTAB;
132              
133 1         2 my $count = 0;
134 1         3 while( @symtab ) {
135 317         585 my $stash = shift @symtab;
136 317 50       832 $stash->type eq "STASH" or die "ARGH! Encountered non-stash ".$stash->desc_addr;
137              
138 317         439 my @more_symtab;
139             my @more_user;
140              
141 317         2881 foreach my $key ( $stash->keys ) {
142 7314         11467 my $value = $stash->value( $key );
143              
144             # Keys ending :: signify sub-stashes
145 7314 100       18979 if( $key =~ m/::$/ ) {
    100          
146 317         750 push @more_symtab, $value->hash;
147             }
148             # Otherwise it might be a glob
149             elsif( $value->type eq "GLOB" ) {
150 6863         7357 my $gv = $value;
151 6863         10710 $gv->{tool_reachable} = REACH_SYMTAB;
152              
153 6863   66     10689 defined $_ and push @more_user, $_ for
154             $gv->scalar, $gv->array, $gv->hash, $gv->code, $gv->io, $gv->form;
155             }
156             # Otherwise it might be a SCALAR/ARRAY/HASH directly in the STASH
157             else {
158 134         163 push @more_user, $value;
159             }
160              
161 7314         10409 $count++;
162 7314 50 33     11469 $progress->( sprintf "Walking symbol table %d...", $count ) if $progress and $count % 1000 == 0;
163             }
164              
165             !$_->{tool_reachable} and
166 317   66     1754 $_->{tool_reachable} = REACH_SYMTAB, push @symtab, $_ for @more_symtab;
167              
168             !$_->{tool_reachable} and
169 317   66     8706 $_->{tool_reachable} = REACH_USER, push @user, $_ for @more_user;
170              
171             !$_->{tool_reachable} and
172 317   66     897 $_->{tool_reachable} = REACH_INTERNAL, push @internal, $_ for
173             $stash->backrefs,
174             $stash->mro_linearall,
175             $stash->mro_linearcurrent,
176             $stash->mro_nextmethod,
177             $stash->mro_isa,
178 0         0 grep { defined } $stash->magic_svs;
179              
180 317         541 $count++;
181 317 50 33     1022 $progress->( sprintf "Walking symbol table %d...", $count ) if $progress and $count % 1000 == 0;
182             }
183             }
184              
185             # Next the reachable user data, recursively
186             {
187 1         2 push @user, $df->main_cv;
  1         6  
188 1         3 my $count = 0;
189 1         5 while( @user ) {
190 27721 50       59628 my $sv = shift @user or next;
191              
192 27721         32610 my @more_user;
193             my @more_internal;
194              
195 27721         47125 for( $sv->type ) {
196 27721 50       92292 if ( $_ eq "REF" ) { push @more_user, $sv->rv if $sv->rv }
  1614 100       2478  
    100          
    100          
    100          
    100          
    50          
    50          
197 376         870 elsif( $_ eq "ARRAY" ) { push @more_user, $sv->elems; }
198 517         1158 elsif( $_ eq "HASH" ) { push @more_user, $sv->values; }
199             elsif( $_ eq "GLOB" ) {
200 4         9 my $gv = $sv;
201 4 50       15 next if $gv->{tool_reachable}; # already on symbol table
202              
203 0         0 warn "Found non-SYMTAB GLOB " . $gv->desc_addr . " user reachable\n";
204             # Hard to know if the GV is being used for GVSV, GVAV, GVHV or GVCV
205 0         0 push @more_user, $gv->scalar, $gv->array, $gv->hash, $gv->code, $gv->egv, $gv->io, $gv->form;
206             }
207             elsif( $_ eq "CODE" ) {
208 5115         5685 my $cv = $sv;
209              
210 5115         5548 my @more_padlist;
211             my @more_lexical;
212              
213 5115         9674 push @more_padlist, $cv->padlist;
214              
215 5115         9295 my $padnames_av = $cv->padnames_av;
216 5115 50       8349 if( $padnames_av ) {
217 0         0 push @more_padlist, $padnames_av, $padnames_av->elems;
218             }
219              
220 5115         8006 foreach my $pad ( $cv->pads ) {
221 2365 100       4622 $pad or next;
222 2364         3232 push @more_padlist, $pad;
223              
224             # PAD slot 0 is always @_
225 2364 100       4794 if( my $argsav = $pad->elem( 0 ) ) {
226 2239         3171 push @more_internal, $argsav;
227             }
228              
229 2364         4428 foreach my $padix ( 1 .. $pad->elems-1 ) {
230 22996 50       28863 my $padname_sv = $padnames_av ? $padnames_av->elem( $padix ) : undef;
231 22996 50 33     35178 my $padname = $padname_sv && $padname_sv->type eq "SCALAR" ?
232             $padname_sv->pv : undef;
233              
234 22996 100       34020 my $padsv = $pad->elem( $padix ) or next;
235 22384 50       40424 $padsv->immortal and next;
236              
237 22384 50 33     40669 if( $padname and $padname eq "&" ) {
    50          
238             # Slots named "&" are closure prototype subs
239 0         0 push @more_user, $padsv;
240             }
241             elsif( $padname ) {
242             # Other named slots are lexical vars
243 0         0 push @more_lexical, $padsv;
244             }
245             else {
246             # Unnamed slots are just part of the padlist
247 22384         31888 push @more_internal, $padsv;
248             }
249             }
250             }
251              
252 5115   66     8829 $_ and push @more_user, $_ for
253             $cv->scope, $cv->constval, $cv->constants, $cv->globrefs;
254              
255             $_ and !$_->{tool_reachable} and
256 5115   66     18275 $_->{tool_reachable} = REACH_PADLIST for @more_padlist;
      100        
257              
258             $_ and !$_->{tool_reachable} and
259 5115   0     10551 $_->{tool_reachable} = REACH_LEXICAL, push @user, $_ for @more_lexical;
      0        
260             }
261             elsif( $_ eq "LVALUE" ) {
262 0         0 my $lv = $sv;
263              
264 0 0       0 push @more_internal, $lv->target if $lv->target;
265             }
266             elsif( $_ =~ m/^(?:UNDEF|BOOL|SCALAR|IO|REGEXP|FORMAT)$/ ) { } # ignore
267              
268 0         0 else { warn "Not sure what to do with user data item ".$sv->desc_addr."\n"; }
269             }
270              
271             $_ and !$_->{tool_reachable} and !$_->immortal and
272 27721   66     122041 $_->{tool_reachable} = REACH_USER, push @user, $_ for @more_user;
      100        
      66        
273              
274             $_ and !$_->{tool_reachable} and !$_->immortal and
275 27721   66     49058 $_->{tool_reachable} = REACH_INTERNAL, push @internal, $_ for
      66        
      66        
276             @more_internal,
277 316         1634 grep { defined } $sv->magic_svs;
278              
279 27721         31875 $count++;
280 27721 50 33     56771 $progress->( sprintf "Marking user reachability %d...", $count ) if $progress and $count % 1000 == 0;
281             }
282             }
283              
284             # Finally internals
285             {
286 1         3 push @internal, pairvalues $df->roots;
  1         11  
  1         6  
287 1         11 my $count = 0;
288 1         5 while( @internal ) {
289 26537 100       42623 my $sv = shift @internal or next;
290 26505 100       50927 next if $sv->{tool_reachable};
291              
292 198         359 $sv->{tool_reachable} = REACH_INTERNAL;
293              
294 198 50       440 push @internal, map { $_->sv ? $_->sv : () } $sv->outrefs;
  573         3311  
295              
296 198         989 $count++;
297 198 50 33     555 $progress->( sprintf "Marking internal reachability %d...", $count ) if $progress and $count % 1000 == 0;
298             }
299             }
300             }
301              
302             =head1 SV METHODS
303              
304             This tool adds the following SV methods.
305              
306             =head2 reachable
307              
308             $r = $sv->reachable
309              
310             Returns true if the SV is reachable from a known root.
311              
312             =cut
313              
314             =head1 AUTHOR
315              
316             Paul Evans
317              
318             =cut
319              
320             0x55AA;