File Coverage

blib/lib/Devel/MAT/Tool/Find.pm
Criterion Covered Total %
statement 113 287 39.3
branch 0 120 0.0
condition 0 49 0.0
subroutine 38 63 60.3
pod 0 4 0.0
total 151 523 28.8


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, 2017-2020 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT::Tool::Find 0.49;
7              
8 5     5   3511 use v5.14;
  5         16  
9 5     5   24 use warnings;
  5         7  
  5         139  
10 5     5   23 use base qw( Devel::MAT::Tool );
  5         8  
  5         473  
11              
12 5     5   37 use Scalar::Util qw( blessed );
  5         18  
  5         360  
13              
14 5     5   28 use constant CMD => "find";
  5         15  
  5         400  
15 5     5   32 use constant CMD_DESC => "List SVs matching given criteria";
  5         10  
  5         302  
16              
17 5         360 use constant CMD_OPTS => (
18             count => { help => "Just print a count of the matching SVs",
19             alias => "C" },
20 5     5   33 );
  5         7  
21              
22             use Module::Pluggable
23 5         59 sub_name => "FILTERS",
24             search_path => [ "Devel::MAT::Tool::Find::filter" ],
25 5     5   35 require => 1;
  5         7  
26              
27             =head1 NAME
28              
29             C - list SVs matching given criteria
30              
31             =head1 DESCRIPTION
32              
33             This C tool provides a command to search for SVs matching given
34             criteria.
35              
36             =cut
37              
38             =head1 COMMANDS
39              
40             =cut
41              
42             =head2 find
43              
44             pmat> find io
45             IO()=IO::File at 0x55a7e4d88760: ifileno=1 ofileno=1
46             ...
47              
48             Prints a list of all the SVs that match the given filter criteria.
49              
50             Takes the following named options:
51              
52             =over 4
53              
54             =item --count, -C
55              
56             Just count the matching SVs and print the total
57              
58             =back
59              
60             =cut
61              
62             # TODO(leonerd): This is ugly; taking over ->run_cmd directly. See if we can
63             # integrate it better
64             sub run_cmd
65             {
66 0     0 0   my $self = shift;
67 0           my ( $inv ) = @_;
68              
69 0           my %opts = %{ $self->get_opts_from_inv( $inv, { $self->CMD_OPTS },
  0            
70             permute => 0,
71             ) };
72              
73 0           my @filters;
74 0           while( length $inv->peek_remaining ) {
75 0           push @filters, $self->build_filter( $inv );
76             }
77              
78 0 0         if( $opts{count} ) {
79 0           my $count = 0;
80 0           SV: foreach my $sv ( $self->df->heap ) {
81 0           foreach my $filter ( @filters ) {
82 0 0         my $ret = $filter->( $sv ) or next SV;
83 0 0 0       if( !blessed $ret and ref $ret eq "HASH" ) {
84 0 0         $sv = $ret->{sv} if $ret->{sv};
85             }
86             }
87              
88 0           $count++;
89             }
90              
91 0 0         Devel::MAT::Cmd->printf( "Total: %s SVs\n", $count ) if $opts{count};
92 0           return;
93             }
94              
95 0           my @svs = $self->df->heap;
96 0           my ( $sv, @output );
97             Devel::MAT::Tool::more->paginate( sub {
98 0     0     my ( $count ) = @_;
99 0           SV: while( $sv = shift @svs ) {
100 0           @output = ();
101              
102 0           foreach my $filter ( @filters ) {
103 0 0         my $ret = $filter->( $sv ) or next SV;
104             # Allow filters to alter the search as we go
105 0 0 0       if( !blessed $ret and ref $ret eq "HASH" ) {
106 0 0         $sv = $ret->{sv} if $ret->{sv};
107 0 0         push @output, $ret->{output} if $ret->{output};
108             }
109             else {
110 0           push @output, $ret;
111             }
112             }
113              
114 0           my $fmt = "%s";
115 0 0         $fmt .= ": " . join( " ", ( "%s" ) x @output ) if @output;
116              
117 0           Devel::MAT::Cmd->printf( "$fmt\n",
118             Devel::MAT::Cmd->format_sv( $sv ),
119             @output
120             );
121              
122 0 0         last SV unless $count--;
123             }
124              
125 0           return !!@svs;
126 0           } );
127             }
128              
129             sub help_cmd
130             {
131 0     0 0   Devel::MAT::Cmd->printf( "\nSYNOPSIS:\n" );
132 0           Devel::MAT::Cmd->printf( " find [FILTER...]\n" );
133              
134 0           Devel::MAT::Cmd->printf( "\nFILTERS:\n" );
135              
136 0           foreach my $pkg ( FILTERS ) {
137 0           my $name = $pkg =~ s/^Devel::MAT::Tool::Find::filter:://r;
138              
139 0           Devel::MAT::Cmd->printf( " %s %s - %s\n",
140             Devel::MAT::Cmd->format_note( "find" ),
141             Devel::MAT::Cmd->format_note( $name ),
142             $pkg->FILTER_DESC,
143             );
144             }
145             }
146              
147             # to make help work
148 0     0 0   sub find_subcommand { return "Devel::MAT::Tool::Find::filter::$_[1]" }
149              
150             sub build_filter
151             {
152 0     0 0   my $self = shift;
153 0           my ( $inv ) = @_;
154              
155 0           my $name = $inv->pull_token;
156 0           my $filterpkg = "Devel::MAT::Tool::Find::filter::$name";
157 0 0         $filterpkg->can( "build" ) or
158             die "Unknown filter type '$name'";
159              
160 0           my @args;
161              
162 0 0         if( my %optspec = $filterpkg->FILTER_OPTS ) {
163 0           push @args, $self->get_opts_from_inv( $inv, \%optspec );
164             }
165              
166 0 0         if( my @argspec = $filterpkg->FILTER_ARGS ) {
167 0           push @args, $self->get_args_from_inv( $inv, @argspec );
168             }
169              
170 0           return $filterpkg->build( $inv, @args );
171             }
172              
173             =head1 FILTERS
174              
175             =cut
176              
177             package # hide
178             Devel::MAT::Tool::Find::filter;
179              
180 0     0     sub CMD_DESC { return "find " . shift->FILTER_DESC }
181              
182 5     5   4349 use constant FILTER_OPTS => ();
  5         10  
  5         390  
183 0     0     sub CMD_OPTS { shift->FILTER_OPTS }
184              
185 5     5   31 use constant CMD_ARGS_SV => 0;
  5         7  
  5         226  
186              
187 5     5   24 use constant FILTER_ARGS => ();
  5         11  
  5         393  
188 0     0     sub CMD_ARGS { shift->FILTER_ARGS }
189              
190             package # hide
191             Devel::MAT::Tool::Find::filter::num;
192 5     5   44 use base qw( Devel::MAT::Tool::Find::filter );
  5         8  
  5         1735  
193              
194 5     5   33 use constant FILTER_DESC => "Numerical (IV, UV or NV) SVs";
  5         8  
  5         341  
195              
196 5         286 use constant FILTER_OPTS => (
197             iv => { help => "Include IVs" },
198             uv => { help => "Include UVs" },
199             nv => { help => "Include NVs" },
200 5     5   32 );
  5         8  
201              
202 5         1505 use constant FILTER_ARGS => (
203             { name => "value", help => "match value" },
204 5     5   27 );
  5         10  
205              
206             =head2 num
207              
208             pmat> find num
209             SCALAR(UV) at 0x555555a1e9c0: 5
210             SCALAR(UV) at 0x555555c4f1b0: 2
211             SCALAR(UV) at 0x555555aa0dc0: 18446744073709551615
212              
213             Prints a list of all the scalar SVs that have a numerical value, optionally
214             filtering for only an exact value.
215              
216             Takes the following named options:
217              
218             =over 4
219              
220             =item --nv, --iv, --uv
221              
222             Find only numerical SVs of the given types. If no options present, any
223             numerical SV will be found.
224              
225             =back
226              
227             =cut
228              
229             sub build
230             {
231 0     0     my $self = shift;
232 0           shift; # inv
233 0           my %opts = %{ +shift };
  0            
234 0           my ( $value ) = @_;
235              
236             $opts{iv} or $opts{uv} or $opts{nv} or
237 0 0 0       $opts{iv} = $opts{uv} = $opts{nv} = 1;
      0        
238              
239             return sub {
240 0     0     my ( $sv ) = @_;
241 0 0         return unless $sv->type eq "SCALAR";
242              
243 0 0 0       if( $opts{nv} and defined( my $nv = $sv->nv ) ) {
244 0 0 0       defined $value and $nv != $value and return;
245 0           return Devel::MAT::Cmd->format_value( $nv, nv => 1 );
246             }
247              
248 0 0 0       if( $opts{iv} and defined( my $iv = $sv->iv ) ) {
249 0 0 0       defined $value and $iv != $value and return;
250 0           return Devel::MAT::Cmd->format_value( $iv, iv => 1 );
251             }
252              
253 0 0 0       if( $opts{uv} and defined( my $uv = $sv->uv ) ) {
254 0 0 0       defined $value and $uv != $value and return;
255 0           return Devel::MAT::Cmd->format_value( $uv, uv => 1 );
256             }
257 0           };
258             }
259              
260             package # hide
261             Devel::MAT::Tool::Find::filter::pv;
262 5     5   57 use base qw( Devel::MAT::Tool::Find::filter );
  5         10  
  5         1285  
263              
264 5     5   32 use constant FILTER_DESC => "PV (string) SVs";
  5         9  
  5         371  
265              
266 5         352 use constant FILTER_OPTS => (
267             eq => { help => "Pattern is an exact equality match" },
268             regexp => { help => "Pattern is a regular expression",
269             alias => "r" },
270             ignorecase => { help => "Match case-insensitively",
271             alias => "i" },
272 5     5   28 );
  5         10  
273              
274 5         1267 use constant FILTER_ARGS => (
275             { name => "pattern", help => "string pattern", required => 1 },
276 5     5   55 );
  5         8  
277              
278             =head2 pv
279              
280             pmat> find pv "boot"
281             SCALAR(PV) at 0x556e4737d968: "boot_Devel::MAT::Dumper"
282             SCALAR(PV) at 0x556e4733a160: "boot_Cwd"
283             ...
284              
285             Prints a list of all the scalar SVs that have a PV (string value) matching the
286             supplied pattern. Normally, the pattern is interpreted as a substring match,
287             but the C<--eq> and C<--regexp> options can alter this.
288              
289             Takes the following named options:
290              
291             =over 4
292              
293             =item --eq
294              
295             Interpret the pattern as a full string equality match, instead of substring.
296              
297             =item --regexp, -r
298              
299             Interpret the pattern as a regular expression, instead of a literal substring.
300              
301             =item --ignorecase, -i
302              
303             Match case-insensitively, for any of substring, equality or regexp match.
304              
305             =back
306              
307             =cut
308              
309             sub build
310             {
311 0     0     my $self = shift;
312 0           shift; # inv
313 0           my %opts = %{ +shift };
  0            
314 0           my ( $pattern ) = @_;
315              
316 0 0         my $flags = $opts{ignorecase} ? "i" : "";
317              
318 0 0         if( $opts{eq} ) {
    0          
319 0           $pattern = qr/(?$flags)^\Q$pattern\E$/;
320             }
321             elsif( $opts{regexp} ) {
322 0           $pattern = qr/(?$flags)$pattern/;
323             }
324             else {
325             # substring
326 0           $pattern = qr/(?$flags)\Q$pattern\E/;
327             }
328              
329             return sub {
330 0     0     my ( $sv ) = @_;
331 0 0         return unless $sv->type eq "SCALAR";
332 0 0         return unless defined( my $pv = $sv->pv );
333 0 0         return unless $pv =~ $pattern;
334              
335 0           return Devel::MAT::Cmd->format_value( $pv, pv => 1 );
336 0           };
337             }
338              
339             package # hide
340             Devel::MAT::Tool::Find::filter::cv;
341 5     5   31 use base qw( Devel::MAT::Tool::Find::filter );
  5         9  
  5         1231  
342              
343 5     5   30 use constant FILTER_DESC => "Code CVs";
  5         19  
  5         354  
344              
345 5         1295 use constant FILTER_OPTS => (
346             xsub => { help => "Is an XSUB" },
347             package => { help => "In the given package",
348             type => "s",
349             alias => "p" },
350             file => { help => "Location is the given file",
351             type => "s",
352             alias => "f" },
353 5     5   32 );
  5         10  
354              
355             sub build
356             {
357 0     0     my $self = shift;
358 0           my $inv = shift;
359 0           my %opts = %{ +shift };
  0            
360              
361             return sub {
362 0     0     my ( $sv ) = @_;
363 0 0         return unless $sv->type eq "CODE";
364 0 0         if( $opts{xsub} ) {
365 0 0         return if !$sv->is_xsub;
366             }
367 0 0         if( $opts{package} ) {
368 0 0         my $stash = $sv->glob ? $sv->glob->stash : return;
369 0 0         return if $stash->stashname ne $opts{package};
370             }
371 0 0         if( $opts{file} ) {
372 0 0         return if $sv->file ne $opts{file};
373             }
374              
375             # Selected
376 0 0         if( my $symname = $sv->symname ) {
377 0           return Devel::MAT::Cmd->format_symbol( $symname );
378             }
379             else {
380 0           return "__ANON__";
381             }
382 0           };
383             }
384              
385             package # hide
386             Devel::MAT::Tool::Find::filter::io;
387 5     5   44 use base qw( Devel::MAT::Tool::Find::filter );
  5         18  
  5         1222  
388              
389 5     5   30 use constant FILTER_DESC => "IO SVs";
  5         9  
  5         334  
390              
391 5         1680 use constant FILTER_OPTS => (
392             fileno => { help => "Match only this filenumber",
393             type => "i",
394             alias => "f" },
395 5     5   30 );
  5         8  
396              
397             =head2 io
398              
399             pmat> find io
400             IO()=IO::File at 0x55a7e4d88760: ifileno=1 ofileno=1
401             ...
402              
403             pmat> find io -f 2
404             IO()=IO::File at 0x55582b87f430: ifileno=2 ofileno=2
405              
406             Searches for IO handles
407              
408             Takes the following named options:
409              
410             =over 4
411              
412             =item --fileno, -f INT
413              
414             Match only IO handles associated with the given filenumber.
415              
416             =back
417              
418             =cut
419              
420             sub build
421             {
422 0     0     my $self = shift;
423 0           my $inv = shift;
424 0           my %opts = %{ +shift };
  0            
425              
426             # Back-compat
427 0 0 0       if( !defined $opts{fileno} and ( $inv->peek_token // "" ) =~ m/^\d+$/ ) {
      0        
428 0           $opts{fileno} = $inv->pull_token;
429             }
430              
431 0 0         if( defined( my $fileno = $opts{fileno} ) ) {
432             return sub {
433 0     0     my ( $sv ) = @_;
434 0 0         return unless $sv->type eq "IO";
435              
436 0           my $imatch = $sv->ifileno == $fileno;
437 0           my $omatch = $sv->ofileno == $fileno;
438 0 0 0       return unless $imatch or $omatch;
439              
440 0 0         return String::Tagged->from_sprintf( "ifileno=%s ofileno=%s",
    0          
441             $imatch ? Devel::MAT::Cmd->format_note( $sv->ifileno ) : $sv->ifileno,
442             $omatch ? Devel::MAT::Cmd->format_note( $sv->ofileno ) : $sv->ofileno,
443             );
444             }
445 0           }
446             else {
447             return sub {
448 0     0     my ( $sv ) = @_;
449 0 0         return unless $sv->type eq "IO";
450 0           return String::Tagged->from_sprintf( "ifileno=%s ofileno=%s",
451             $sv->ifileno,
452             $sv->ofileno,
453             );
454             }
455 0           }
456             }
457              
458             package # hide
459             Devel::MAT::Tool::Find::filter::blessed;
460 5     5   41 use base qw( Devel::MAT::Tool::Find::filter );
  5         11  
  5         1266  
461              
462             =head2 blessed
463              
464             pmat> find blessed Config
465             HASH(26)=Config at 0x55bd56c28930
466              
467             Searches for SVs blessed into the given package name.
468              
469             =cut
470              
471 5     5   30 use constant FILTER_DESC => "blessed SVs";
  5         9  
  5         291  
472              
473 5         874 use constant FILTER_ARGS => (
474             { name => "package", help => "the blessed package", required => 1 },
475 5     5   30 );
  5         9  
476              
477             sub build
478             {
479 0     0     my $self = shift;
480 0           my ( $inv, $package ) = @_;
481              
482 0 0         defined $package or
483             die "Expected package name for 'blessed' filter";
484              
485             return sub {
486 0     0     my ( $sv ) = @_;
487 0 0         return unless my $stash = $sv->blessed;
488 0 0         return unless $stash->stashname eq $package;
489 0           return Devel::MAT::Cmd->format_value( $stash->stashname );
490 0           };
491             }
492              
493             package # hide
494             Devel::MAT::Tool::Find::filter::lexical;
495 5     5   34 use base qw( Devel::MAT::Tool::Find::filter );
  5         10  
  5         1359  
496              
497             =head2 lexical
498              
499             pmat> find lexical $x
500             UNDEF() at 0x56426e97c8b0: $x at depth 1 of CODE(PP) at 0x56426e97c5e0
501             ...
502              
503             Searches for SVs that are lexical variables of the given name.
504              
505             =cut
506              
507 5     5   42 use constant FILTER_DESC => "lexical variables";
  5         19  
  5         312  
508              
509 5         321 use constant FILTER_ARGS => (
510             { name => "name", help => "the variable name", required => 1 },
511 5     5   29 );
  5         12  
512              
513 5         1394 use constant FILTER_OPTS => (
514             inactive => { help => "Include variables in non-live pads",
515             alias => "I" },
516 5     5   29 );
  5         8  
517              
518             sub build
519             {
520 0     0     my $self = shift;
521 0           my $inv = shift;
522 0           my %opts = %{ +shift };
  0            
523 0           my ( $name ) = @_;
524              
525 0 0         defined $name or
526             die "Expected variable name for 'lexical' filter";
527              
528             # We'll actually match pad which contains such a lexical. then redirect the
529             # search onto the SV itself
530             return sub {
531 0     0     my ( $pad ) = @_;
532 0 0         return unless $pad->type eq "PAD";
533 0 0         return unless my $sv = $pad->maybe_lexvar( $name );
534              
535 0           my $cv = $pad->padcv;
536              
537 0           my $depth;
538 0           my @pads = $cv->pads;
539             $pad == $pads[$_] and $depth = $_+1 and last
540 0   0       for 0 .. $#pads;
      0        
541              
542             # This isn't a real hit unless the pad is live
543 0           my $is_live = $depth <= $cv->depth;
544 0 0 0       return unless $is_live || $opts{inactive};
545              
546             return {
547 0 0         sv => $sv,
548             output => String::Tagged->from_sprintf( "%s at depth %d%s of %s",
549             Devel::MAT::Cmd->format_note( $name, 1 ),
550             $depth, $is_live ? "" : Devel::MAT::Cmd->format_note( " [inactive]", 2 ),
551             Devel::MAT::Cmd->format_sv( $cv )
552             ),
553             };
554 0           };
555             }
556              
557             package # hide
558             Devel::MAT::Tool::Find::filter::struct;
559 5     5   32 use base qw( Devel::MAT::Tool::Find::filter );
  5         9  
  5         1221  
560              
561             =head2 struct
562              
563             pmat> find struct Module::Name/Type
564             C_STRUCT(Module::Name/Type) at 0x55e0c3017bf0: Module::Name/Type
565             ...
566              
567             Searches for SVs that are C structures of the given type name.
568              
569             =cut
570              
571 5     5   30 use constant FILTER_DESC => "structs";
  5         17  
  5         288  
572              
573 5         849 use constant FILTER_ARGS => (
574             { name => "name", help => "the structure type name", required => 1 },
575 5     5   31 );
  5         9  
576              
577             sub build
578             {
579 0     0     my $self = shift;
580 0           my $inv = shift;
581 0           my ( $name ) = @_;
582              
583 0 0         defined $name or
584             die "Expected structure type name for 'struct' filter";
585              
586             return sub {
587 0     0     my ( $struct ) = @_;
588 0 0         return unless $struct->type eq "C_STRUCT";
589 0           my $type = $struct->structtype;
590 0 0         return unless $type->name eq $name;
591              
592 0           return Devel::MAT::Cmd->format_value( $type->name );
593 0           };
594             }
595              
596             package # hide
597             Devel::MAT::Tool::Find::filter::magic;;
598 5     5   40 use base qw( Devel::MAT::Tool::Find::filter );
  5         9  
  5         1212  
599              
600             =head2 magic
601              
602             =cut
603              
604 5     5   32 use constant FILTER_DESC => "SVs with magic";
  5         9  
  5         321  
605              
606 5         1597 use constant FILTER_OPTS => (
607             vtbl => { help => "the VTBL pointer",
608             type => "x",
609             alias => "v" },
610 5     5   41 );
  5         18  
611              
612             sub build
613             {
614 0     0     my $self = shift;
615 0           my $inv = shift;
616 0           my %opts = %{ +shift };
  0            
617              
618 0 0         if( my $vtbl = $opts{vtbl} ) {
619             return sub {
620 0     0     my ( $sv ) = @_;
621 0 0         my @magics = $sv->magic or return;
622 0           foreach my $magic ( @magics ) {
623 0 0 0       next unless defined $magic->vtbl and $magic->vtbl == $vtbl;
624              
625 0           my $ret = String::Tagged->from_sprintf( "magic type '%s'",
626             $magic->type,
627             );
628              
629 0 0         $ret .= ", with object " . Devel::MAT::Cmd->format_sv( $magic->obj ) if $magic->obj;
630              
631 0 0         $ret .= ", with pointer " . Devel::MAT::Cmd->format_sv( $magic->ptr ) if $magic->ptr;
632              
633 0           return $ret;
634             }
635 0           };
636             }
637              
638 0           die "Expected --vtbl\n";
639             }
640              
641             =head1 AUTHOR
642              
643             Paul Evans
644              
645             =cut
646              
647             0x55AA;