File Coverage

blib/lib/Devel/MAT.pm
Criterion Covered Total %
statement 196 267 73.4
branch 71 156 45.5
condition 54 83 65.0
subroutine 31 38 81.5
pod 10 12 83.3
total 362 556 65.1


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-2022 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT 0.50;
7              
8 9     9   872855 use v5.14;
  9         128  
9 9     9   52 use warnings;
  9         23  
  9         273  
10              
11 9     9   48 use Carp;
  9         14  
  9         552  
12 9     9   52 use List::Util qw( first pairs );
  9         22  
  9         575  
13 9     9   4618 use List::UtilsBy qw( sort_by );
  9         17596  
  9         578  
14              
15 9     9   4113 use Syntax::Keyword::Match;
  9         16572  
  9         48  
16              
17 9     9   4314 use Devel::MAT::Dumpfile;
  9         36  
  9         335  
18 9     9   3949 use Devel::MAT::Graph;
  9         27  
  9         306  
19              
20 9     9   3649 use Devel::MAT::InternalTools;
  9         26  
  9         386  
21              
22             use Module::Pluggable
23 9         78 sub_name => "_available_tools",
24             search_path => [ "Devel::MAT::Tool" ],
25 9     9   4596 require => 1;
  9         101333  
26              
27             require XSLoader;
28             XSLoader::load( __PACKAGE__, our $VERSION );
29              
30             =head1 NAME
31              
32             C - Perl Memory Analysis Tool
33              
34             =head1 USER GUIDE
35              
36             B
37              
38             If you are new to the C set of tools, this is probably not the
39             document you want to start with. If you are interested in using C
40             to help diagnose memory-related problems in a F program you instead want
41             to read the user guide, at L.
42              
43             If you are writing tooling modules to extend the abilities of C
44             then this may indeed by the document for you; read on...
45              
46             =head1 DESCRIPTION
47              
48             A C instance loads a heapdump file, and provides a container to
49             store analysis tools to work on it. Tools may be provided that conform to the
50             L API, which can help analyse the data and interact with the
51             explorer user interface by using the methods in the L package.
52              
53             =head2 File Format
54              
55             The dump file format is still under development, so at present no guarantees
56             are made on whether files can be loaded over mismatching versions of
57             C. However, as of version 0.11 the format should be more
58             extensible, allowing new SV fields to be added without breaking loading - older
59             tools will ignore new fields and newer tools will just load undef for fields
60             absent in older files. As the distribution approaches maturity the format will
61             be made more stable.
62              
63             =cut
64              
65             =head1 CONSTRUCTOR
66              
67             =cut
68              
69             =head2 load
70              
71             $pmat = Devel::MAT->load( $path, %args )
72              
73             Loads a heap dump file from the given path, and returns a new C
74             instance wrapping it.
75              
76             =cut
77              
78             sub load
79             {
80 7     7 1 605463 my $class = shift;
81              
82 7         87 my $df = Devel::MAT::Dumpfile->load( @_ );
83              
84 7         141 return bless {
85             df => $df,
86             }, $class;
87             }
88              
89             =head1 METHODS
90              
91             =cut
92              
93             =head2 dumpfile
94              
95             $df = $pmat->dumpfile
96              
97             Returns the underlying L instance backing this analysis
98             object.
99              
100             =cut
101              
102             sub dumpfile
103             {
104 52     52 1 3302 my $self = shift;
105 52         280 return $self->{df};
106             }
107              
108             =head2 available_tools
109              
110             @tools = $pmat->available_tools
111              
112             Lists the L classes that are installed and available.
113              
114             =cut
115              
116             {
117             my @TOOLS;
118             my $TOOLS_LOADED;
119              
120             sub available_tools
121             {
122 3393     3393 1 7780 my $self = shift;
123              
124 3393 100       12906 return @TOOLS if $TOOLS_LOADED;
125              
126 4         26 $TOOLS_LOADED++;
127 4         57 @TOOLS = map { $_ =~ s/^Devel::MAT::Tool:://; $_ } $self->_available_tools;
  136         57347  
  136         231  
128              
129 4         27 foreach my $name ( @TOOLS ) {
130 136         285 my $tool_class = "Devel::MAT::Tool::$name";
131 136 50 33     1313 next unless $tool_class->can( "AUTOLOAD_TOOL" ) and $tool_class->AUTOLOAD_TOOL( $self );
132              
133 0   0     0 $self->{tools}{$name} ||= $tool_class->new( $self );
134             }
135              
136 4         35 return @TOOLS;
137             }
138             }
139              
140             =head2 load_tool
141              
142             $tool = $pmat->load_tool( $name )
143              
144             Loads the named L class.
145              
146             =cut
147              
148             sub load_tool
149             {
150 3389     3389 1 10801 my $self = shift;
151 3389         8991 my ( $name, %args ) = @_;
152              
153             # Ensure tools are 'require'd
154 3389         15051 $self->available_tools;
155              
156 3389         13380 my $tool_class = "Devel::MAT::Tool::$name";
157 3389   66     19393 return $self->{tools}{$name} ||= $tool_class->new( $self, %args );
158             }
159              
160             sub load_tool_for_command
161             {
162 0     0 0 0 my $self = shift;
163 0         0 my ( $cmd, %args ) = @_;
164              
165 0   0     0 return $self->{tools_by_command}{$cmd} ||= do {
166             my $name = first {
167 0     0   0 my $class = "Devel::MAT::Tool::$_";
168 0 0       0 $class->can( "CMD" ) and $class->CMD eq $cmd
169 0 0       0 } $self->available_tools or die "Unrecognised command '$cmd'\n";
170              
171 0         0 $self->load_tool( $name, %args );
172             };
173             }
174              
175             =head2 has_tool
176              
177             $bool = $pmat->has_tool( $name )
178              
179             Returns true if the named tool is already loaded.
180              
181             =cut
182              
183             sub has_tool
184             {
185 0     0 1 0 my $self = shift;
186 0         0 my ( $name ) = @_;
187              
188 0         0 return defined $self->{tools}{$name};
189             }
190              
191             =head2 run_command
192              
193             $pmat->run_command( $inv )
194              
195             Runs a tool command given by the L instance.
196              
197             =cut
198              
199             sub run_command
200             {
201 0     0 1 0 my $self = shift;
202 0         0 my ( $inv, %args ) = @_;
203              
204 0         0 my $cmd = $inv->pull_token;
205              
206             $self->load_tool_for_command( $cmd,
207             progress => $args{process},
208 0         0 )->run_cmd( $inv );
209             }
210              
211             =head2 inref_graph
212              
213             $node = $pmat->inref_graph( $sv, %opts )
214              
215             Traces the tree of inrefs from C<$sv> back towards the known roots, returning
216             a L node object representing it, within a graph of reverse
217             references back to the known roots.
218              
219             This method will load L if it isn't yet loaded.
220              
221             The following named options are recognised:
222              
223             =over 4
224              
225             =item depth => INT
226              
227             If specified, stop recursing after the specified count. A depth of 1 will only
228             include immediately referring SVs, 2 will print the referrers of those, etc.
229             Nodes with inrefs that were trimmed because of this limit will appear to be
230             roots with a special name of C.
231              
232             =item strong => BOOL
233              
234             =item direct => BOOL
235              
236             Specifies the type of inrefs followed. By default all inrefs are followed.
237             Passing C will follow only strong direct inrefs. Passing C
238             will follow only direct inrefs.
239              
240             =item elide => BOOL
241              
242             If true, attempt to neaten up the output by skipping over certain structures.
243              
244             C-type SVs will be skipped to their referrant.
245              
246             Members of the symbol table will be printed as being a 'root' element of the
247             given symbol name.
248              
249             Cs and Cs will be skipped to their referring C, giving
250             shorter output for lexical variables.
251              
252             =back
253              
254             =cut
255              
256             sub inref_graph
257             {
258 3385     3385 1 38813 my $self = shift;
259 3385         18153 my ( $sv, %opts ) = @_;
260              
261 3385   66     14828 my $graph = $opts{graph} //= Devel::MAT::Graph->new( $self->dumpfile );
262              
263             # TODO: allow separate values for these
264 3385         9040 my $elide_rv = $opts{elide};
265 3385         7236 my $elide_sym = $opts{elide};
266 3385         7230 my $elide_pad = $opts{elide};
267              
268 3385         16569 $self->load_tool( "Inrefs" );
269              
270 3385 50       19387 if( $sv->immortal ) {
271 0 0       0 my $desc = $sv->type eq "UNDEF" ? "undef" :
    0          
272             $sv->uv ? "true" :
273             "false";
274 0         0 $graph->add_root( $sv,
275             Devel::MAT::SV::Reference( $desc, strong => undef ) );
276 0         0 return $graph->get_sv_node( $sv );
277             }
278              
279 3385         9121 my $name;
280             my $foundsv;
281 3385 100 100     12672 if( $elide_sym and $name = $sv->symname and
      66        
      100        
      66        
282             $name !~ m/^&.*::__ANON__$/ and
283 10         38 $foundsv = eval { $self->find_symbol( $sv->symname ) } and
284             $foundsv->addr == $sv->addr
285             ) {
286 1         20 $graph->add_root( $sv,
287             Devel::MAT::SV::Reference( "the symbol '" . Devel::MAT::Cmd->format_symbol( $name, $sv ) . "'", strong => undef ) );
288 1         6 return $graph->get_sv_node( $sv );
289             }
290 3384 50 66     11273 if( $elide_sym and $sv->type eq "GLOB" and $name = $sv->stashname ) {
      33        
291 0         0 $graph->add_root( $sv,
292             Devel::MAT::SV::Reference( "the glob '" . Devel::MAT::Cmd->format_symbol( "*$name", $sv ) . '"', strong => undef ) );
293 0         0 return $graph->get_sv_node( $sv );
294             }
295              
296 3384         12690 $graph->add_sv( $sv );
297              
298             my @inrefs = $opts{strong} ? $sv->inrefs_strong :
299 3384 50       24452 $opts{direct} ? $sv->inrefs_direct :
    100          
300             $sv->inrefs;
301              
302             # If we didn't find anything at the given option level, try harder
303 3384 100 100     16151 if( !@inrefs and $opts{strong} ) {
304 1         29 @inrefs = $sv->inrefs_direct;
305             }
306 3384 100 100     12724 if( !@inrefs and $opts{direct} ) {
307 1         12 @inrefs = $sv->inrefs;
308             }
309              
310 3384 100       11503 if( $elide_rv ) {
311 14         31 @inrefs = map { sub {
312 24 100 100 24   68 return $_ unless $_->sv and
      66        
313             $_->sv->type eq "REF" and
314             $_->name eq "the referrant";
315              
316 4         87 my $rv = $_->sv;
317             my @rvrefs = $opts{strong} ? $rv->inrefs_strong :
318 4 0       31 $opts{direct} ? $rv->inrefs_direct :
    50          
319             $rv->inrefs;
320              
321 4 50       13 return $_ unless @rvrefs == 1;
322              
323             # Add 'via RV' marker
324             return map {
325 4         33 Devel::MAT::SV::Reference( Devel::MAT::Cmd->format_note( "(via RV)" ) . " " . $_->name,
  4         21  
326             $_->strength, $_->sv )
327             } @rvrefs;
328 24         470 }->() } @inrefs;
329             }
330              
331 3384 100       10861 if( $elide_pad ) {
332 14         28 @inrefs = map { sub {
333 24 100 100 24   54 return $_ unless $_->sv and
334             $_->sv->type eq "PAD";
335 1         12 my $pad = $_->sv;
336 1         6 my $cv = $pad->padcv;
337             # Even if the CV isn't active, this might be a state variable so we
338             # must always consider pad(1) at least.
339 1   50     6 my ( $depth ) = grep { $cv->pad( $_ ) == $pad } ( 1 .. ( $cv->depth || 1 ) );
  1         6  
340 1         15 return Devel::MAT::SV::Reference( $_->name . " at depth $depth", $_->strength, $cv );
341 24         205 }->() } @inrefs;
342             }
343              
344 3384 50       19746 if( $sv->is_mortal ) {
345 0         0 $graph->add_root( $sv,
346             Devel::MAT::SV::Reference( "a mortal", strong => undef ) );
347             }
348              
349 3384     16844   47521 foreach my $ref ( sort_by { $_->name } @inrefs ) {
  16844         148511  
350 10144 100       128622 if( !defined $ref->sv ) {
351 4         63 $graph->add_root( $sv, $ref );
352 4         25 next;
353             }
354              
355 10140 100 100     92047 if( defined $opts{depth} and not $opts{depth} ) {
356 1679         14773 $graph->add_root( $sv, "EDEPTH" );
357 1679         7421 last;
358             }
359              
360 8461         16311 my @me;
361 8461 100       19748 if( $graph->has_sv( $ref->sv ) ) {
362 5078         12905 $graph->add_ref( $ref->sv, $sv, $ref );
363             # Don't recurse into it as it was already found
364             }
365             else {
366 3383         12707 $graph->add_sv( $ref->sv ); # add first to stop inf. loops
367              
368 3383 100       16248 defined $opts{depth} ? $self->inref_graph( $ref->sv, %opts, depth => $opts{depth}-1 )
369             : $self->inref_graph( $ref->sv, %opts );
370 3383         20876 $graph->add_ref( $ref->sv, $sv, $ref );
371             }
372             }
373              
374 3384         27008 return $graph->get_sv_node( $sv );
375             }
376              
377             =head2 find_symbol
378              
379             $sv = $pmat->find_symbol( $name )
380              
381             Attempts to walk the symbol table looking for a symbol of the given name,
382             which must include the sigil.
383              
384             $Package::Name::symbol_name => to return a SCALAR SV
385             @Package::Name::symbol_name => to return an ARRAY SV
386             %Package::Name::symbol_name => to return a HASH SV
387             &Package::Name::symbol_name => to return a CODE SV
388              
389             =cut
390              
391             sub find_symbol
392             {
393 30     30 1 4567 my $self = shift;
394 30         69 my ( $name ) = @_;
395              
396 30 50       286 my ( $sigil, $globname ) = $name =~ m/^([\$\@%&])(.*)$/ or
397             croak "Could not parse sigil from $name";
398              
399 30         112 my $stashvalue = $self->find_stashvalue( $globname );
400              
401             # Perl 5.22 may take CODE shortcuts
402 30 50 66     150 if( $sigil eq '&' and $stashvalue->type eq "REF" ) {
403 0         0 return $stashvalue->rv;
404             }
405              
406 30 50       106 $stashvalue->type eq "GLOB" or
407             croak "$globname is not a GLOB";
408              
409 30 50       136 my $slot = ( $sigil eq '$' ) ? "scalar" :
    100          
    100          
    100          
410             ( $sigil eq '@' ) ? "array" :
411             ( $sigil eq '%' ) ? "hash" :
412             ( $sigil eq '&' ) ? "code" :
413             die "ARGH"; # won't happen
414              
415 30 100       142 my $sv = $stashvalue->$slot or
416             croak "\*$globname has no $slot slot";
417 21         106 return $sv;
418             }
419              
420             =head2 find_glob
421              
422             $gv = $pmat->find_glob( $name )
423              
424             Attempts to walk the symbol table looking for a symbol of the given name,
425             returning the C object if found.
426              
427             =head2 find_stash
428              
429             $stash = $pmat->find_stash( $name )
430              
431             Attempts to walk the symbol table looking for a stash of the given name.
432              
433             =cut
434              
435             sub find_stashvalue
436             {
437 50     50 0 83 my $self = shift;
438 50         106 my ( $name ) = @_;
439              
440 50         340 my ( $parent, $shortname ) = $name =~ m/^(?:(.*)::)?(.+?)$/;
441              
442 50         92 my $stash;
443 50 100 100     214 if( defined $parent and length $parent ) {
444 14         57 $stash = $self->find_stash( $parent );
445             }
446             else {
447 36         128 $stash = $self->dumpfile->defstash;
448             }
449              
450 50 50       239 my $sv = $stash->value( $shortname ) or
451             croak $stash->stashname . " has no symbol $shortname";
452 50         170 return $sv;
453             }
454              
455             sub find_glob
456             {
457 20     20 1 4432 my $self = shift;
458 20         45 my ( $name ) = @_;
459              
460 20 50       77 my $sv = $self->find_stashvalue( $name ) or return;
461 20 50       141 $sv->type eq "GLOB" or
462             croak "$name is not a GLOB";
463              
464 20         58 return $sv;
465             }
466              
467             sub find_stash
468             {
469 15     15 1 715 my $self = shift;
470 15         43 my ( $name ) = @_;
471              
472 15         70 my $gv = $self->find_glob( $name . "::" );
473 15   33     62 return $gv->hash ||
474             croak "$name has no hash";
475             }
476              
477             # Some base implementations of Devel::MAT::Cmd formatters
478              
479             push @Devel::MAT::Cmd::ISA, qw( Devel::MAT::Cmd::_base );
480              
481             package
482             Devel::MAT::Cmd::_base;
483              
484 9     9   21093 use B qw( perlstring );
  9         27  
  9         654  
485 9     9   66 use List::Util qw( max );
  9         33  
  9         16083  
486              
487             sub print_table
488             {
489 6     6   3776 my $self = shift;
490 6         15 my ( $rows, %opts ) = @_;
491              
492 6 100       17 if( $opts{headings} ) {
493 1         3 my @headings = map { $self->format_heading( $_ ) } @{ $opts{headings} };
  2         9  
  1         4  
494 1         7 $rows = [ \@headings, @$rows ];
495             }
496              
497 6 50       14 return unless @$rows;
498              
499 6         15 my $cols = max map { scalar @$_ } @$rows;
  9         27  
500              
501             my @colwidths = map {
502 6         16 my $colidx = $_;
  17         23  
503             # TODO: consider a unicode/terminal-aware version of length here
504 17   50     37 max map { length($_->[$colidx]) // 0 } @$rows;
  25         71  
505             } 0 .. $cols-1;
506              
507 6   100     24 my $align = $opts{align} // "";
508 6 100       22 $align = [ ( $align ) x $cols ] if !ref $align;
509              
510 6   100     20 my $sep = $opts{sep} // " ";
511 6 50       18 $sep = [ ( $sep ) x ($cols - 1) ] if !ref $sep;
512              
513 6   100     11 my @leftalign = map { ($align->[$_]//"") ne "right" } 0 .. $cols-1;
  17         45  
514              
515             my $format = join( "",
516             ( " " x ( $opts{indent} // 0 ) ),
517             ( map {
518 6   100     27 my $col = $_;
  17         23  
519 17         20 my $width = $colwidths[$col];
520 17 100       84 my $flags = $leftalign[$col] ? "-" : "";
521             # If final column should be left-aligned don't bother with width
522 17 50 66     44 $width = "" if $col == $cols-1 and $leftalign[$col];
523              
524 17 100       61 ( $col ? $sep->[$col-1] : "" ) . "%${flags}${width}s"
525             } 0 .. $cols-1 ),
526             ) . "\n";
527              
528 6         18 foreach my $row ( @$rows ) {
529 9         45 my @row = @$row;
530 9 50       17 @row or @row = map { "-"x$colwidths[$_] } ( 0 .. $cols-1 );
  0         0  
531 9         20 push @row, "" while @row < $cols; # pad with spaces
532 9         25 $self->printf( $format, @row );
533             }
534             }
535              
536             sub format_note
537             {
538 112     112   161 shift;
539 112         210 my ( $str, $idx ) = @_;
540              
541 112         337 return $str;
542             }
543              
544             sub _format_sv
545             {
546 4     4   7 shift;
547 4         8 my ( $ret ) = @_;
548              
549 4         16 return $ret;
550             }
551              
552             sub format_sv
553             {
554 4     4   31 shift;
555 4         9 my ( $sv ) = @_;
556              
557 4         22 my $ret = $sv->desc;
558              
559 4 50       17 if( my $blessed = $sv->blessed ) {
560 0         0 $ret .= "=" . Devel::MAT::Cmd->format_symbol( $blessed->stashname, $blessed );
561             }
562              
563 4         33 $ret .= sprintf " at %#x", $sv->addr;
564              
565 4 100       28 if( my $rootname = $sv->rootname ) {
566 2         7 $ret .= "=" . Devel::MAT::Cmd->format_note( $rootname, 1 );
567             }
568              
569 4         21 return Devel::MAT::Cmd->_format_sv( $ret, $sv );
570             }
571              
572             sub _format_value
573             {
574 6597501     6597501   7946900 shift;
575 6597501         9052770 my ( $val ) = @_;
576              
577 6597501         23361075 return $val;
578             }
579              
580             sub format_value
581             {
582 6597501     6597501   8179238 shift;
583 6597501         11667031 my ( $val, %opts ) = @_;
584              
585 6597501         7965364 my $text;
586 6597501 100       11470114 if( $opts{key} ) {
    50          
    0          
587 2236971         2926999 my $strval = $val;
588 2236971 50 33     7377676 if( $opts{stash} && $strval =~ m/^([\x00-\x1f])([a-zA-Z0-9_]*)$/ ) {
    100          
589 0         0 $strval = "^" . chr( 64 + ord $1 ) . $2;
590             }
591             elsif( $strval !~ m/^[a-zA-Z_][a-zA-Z0-9_]*$/ ) {
592 484132         1728148 $strval = perlstring( $val );
593             }
594              
595 2236971         3742060 return "{" . Devel::MAT::Cmd->_format_value( $strval ) . "}";
596             }
597             elsif( $opts{index} ) {
598 4360530         6824000 return "[" . Devel::MAT::Cmd->_format_value( $val+0 ) . "]";
599             }
600             elsif( $opts{pv} ) {
601 0         0 my $truncated;
602 0 0 0     0 if( my $maxlen = $opts{maxlen} // 64 ) {
603 0 0       0 ( $truncated = length $val > $maxlen ) and
604             substr( $val, $maxlen ) = "";
605             }
606              
607 0 0       0 return Devel::MAT::Cmd->_format_value(
608             perlstring( $val ) . ( $truncated ? "..." : "" )
609             );
610             }
611             else {
612 0         0 return Devel::MAT::Cmd->_format_value( $val );
613             }
614             }
615              
616             sub format_symbol
617             {
618 1     1   2 shift;
619 1         5 my ( $name ) = @_;
620              
621 1         6 return $name;
622             }
623              
624             sub format_bytes
625             {
626 0     0     shift;
627 0           my ( $bytes ) = @_;
628              
629 0 0         if( $bytes < 1024 ) {
630 0           return sprintf "%d bytes", $bytes;
631             }
632 0 0         if( $bytes < 1024**2 ) {
633 0           return sprintf "%.1f KiB", $bytes / 1024;
634             }
635 0 0         if( $bytes < 1024**3 ) {
636 0           return sprintf "%.1f MiB", $bytes / 1024**2;
637             }
638 0 0         if( $bytes < 1024**4 ) {
639 0           return sprintf "%.1f GiB", $bytes / 1024**3;
640             }
641 0           return sprintf "%.1f TiB", $bytes / 1024**4;
642             }
643              
644             sub format_sv_with_value
645             {
646 0     0     my $self = shift;
647 0           my ( $sv ) = @_;
648              
649 0           my $repr = $self->format_sv( $sv );
650              
651             match( $sv->type : eq ) {
652             case( "SCALAR" ) {
653 0           my @reprs;
654              
655             my $num;
656 0 0 0       defined( $num = $sv->nv // $sv->uv ) and
657             push @reprs, $self->format_value( $num, nv => 1 );
658              
659 0 0         defined $sv->pv and
660             push @reprs, $self->format_value( $sv->pv, pv => 1 );
661              
662             # Dualvars
663 0 0         return "$repr = $reprs[0] / $reprs[1]" if @reprs > 1;
664              
665 0 0         return "$repr = $reprs[0]" if @reprs;
666             }
667             case( "BOOL" ) {
668 0 0         return "$repr = " . $self->format_value( $sv->uv ? "true" : "false" );
669             }
670             case( "REF" ) {
671             #return "REF => NULL" if !$sv->rv;
672 0 0         return "$repr => " . $self->format_sv_with_value( $sv->rv ) if $sv->rv;
673             }
674             case( "ARRAY" ) {
675 0 0         return $repr if $sv->blessed;
676              
677 0           my $n_elems = $sv->elems;
678 0 0         return "$repr = []" if !$n_elems;
679              
680 0           my $elem = $self->format_sv( $sv->elem( 0 ) );
681 0 0         $elem .= ", ..." if $n_elems > 1;
682              
683 0           return "$repr = [$elem]";
684             }
685             case( "HASH" ) {
686 0 0         return $repr if $sv->blessed;
687              
688 0           my $n_values = $sv->values;
689 0 0         return "$repr = {}" if !$n_values;
690              
691 0           my $key = ( $sv->keys )[0]; # pick one at random
692 0           my $value = $self->format_value( $key, key => 1 ) . " => " . $self->format_sv( $sv->value( $key ) );
693 0 0         $value .= ", ..." if $n_values > 1;
694              
695 0           return "$repr = {$value}";
696             }
697             case( "GLOB" ) {
698 0           return "$repr is " . $self->format_symbol( "*" . $sv->stashname, $sv );
699             }
700 0 0         case( "STASH" ) {
    0          
    0          
    0          
    0          
    0          
    0          
701 0           return "$repr is " . $self->format_symbol( $sv->stashname, $sv );
702             }
703             }
704              
705 0           return $repr;
706             }
707              
708             sub format_heading
709             {
710 0     0     shift;
711 0           my ( $text, $level ) = @_;
712              
713 0           return "$text";
714             }
715              
716             =head1 AUTHOR
717              
718             Paul Evans
719              
720             =cut
721              
722             0x55AA;