File Coverage

blib/lib/Devel/MAT.pm
Criterion Covered Total %
statement 195 265 73.5
branch 70 154 45.4
condition 54 83 65.0
subroutine 31 38 81.5
pod 10 12 83.3
total 360 552 65.2


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.49;
7              
8 9     9   557593 use v5.14;
  9         94  
9 9     9   39 use warnings;
  9         13  
  9         245  
10              
11 9     9   38 use Carp;
  9         12  
  9         502  
12 9     9   51 use List::Util qw( first pairs );
  9         14  
  9         650  
13 9     9   3863 use List::UtilsBy qw( sort_by );
  9         14310  
  9         523  
14              
15 9     9   3435 use Syntax::Keyword::Match;
  9         13107  
  9         44  
16              
17 9     9   3515 use Devel::MAT::Dumpfile;
  9         26  
  9         300  
18 9     9   3778 use Devel::MAT::Graph;
  9         21  
  9         269  
19              
20 9     9   3143 use Devel::MAT::InternalTools;
  9         22  
  9         340  
21              
22             use Module::Pluggable
23 9         62 sub_name => "_available_tools",
24             search_path => [ "Devel::MAT::Tool" ],
25 9     9   3630 require => 1;
  9         84979  
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 445259 my $class = shift;
81              
82 7         83 my $df = Devel::MAT::Dumpfile->load( @_ );
83              
84 7         107 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 1906 my $self = shift;
105 52         276 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 6420 my $self = shift;
123              
124 3393 100       10409 return @TOOLS if $TOOLS_LOADED;
125              
126 4         23 $TOOLS_LOADED++;
127 4         36 @TOOLS = map { $_ =~ s/^Devel::MAT::Tool:://; $_ } $self->_available_tools;
  136         46754  
  136         182  
128              
129 4         40 foreach my $name ( @TOOLS ) {
130 136         219 my $tool_class = "Devel::MAT::Tool::$name";
131 136 50 33     971 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         24 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 8728 my $self = shift;
151 3389         8833 my ( $name, %args ) = @_;
152              
153             # Ensure tools are 'require'd
154 3389         13157 $self->available_tools;
155              
156 3389         8625 my $tool_class = "Devel::MAT::Tool::$name";
157 3389   66     15909 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 30702 my $self = shift;
259 3385         15992 my ( $sv, %opts ) = @_;
260              
261 3385   66     11210 my $graph = $opts{graph} //= Devel::MAT::Graph->new( $self->dumpfile );
262              
263             # TODO: allow separate values for these
264 3385         5885 my $elide_rv = $opts{elide};
265 3385         7750 my $elide_sym = $opts{elide};
266 3385         6483 my $elide_pad = $opts{elide};
267              
268 3385         12404 $self->load_tool( "Inrefs" );
269              
270 3385 50       13425 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         7350 my $name;
280             my $foundsv;
281 3385 100 100     9128 if( $elide_sym and $name = $sv->symname and
      66        
      100        
      66        
282             $name !~ m/^&.*::__ANON__$/ and
283 10         23 $foundsv = eval { $self->find_symbol( $sv->symname ) } and
284             $foundsv->addr == $sv->addr
285             ) {
286 1         13 $graph->add_root( $sv,
287             Devel::MAT::SV::Reference( "the symbol '" . Devel::MAT::Cmd->format_symbol( $name, $sv ) . "'", strong => undef ) );
288 1         9 return $graph->get_sv_node( $sv );
289             }
290 3384 50 66     8823 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         8434 $graph->add_sv( $sv );
297              
298             my @inrefs = $opts{strong} ? $sv->inrefs_strong :
299 3384 50       18627 $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     13404 if( !@inrefs and $opts{strong} ) {
304 1         8 @inrefs = $sv->inrefs_direct;
305             }
306 3384 100 100     8239 if( !@inrefs and $opts{direct} ) {
307 1         7 @inrefs = $sv->inrefs;
308             }
309              
310 3384 100       9862 if( $elide_rv ) {
311 14         18 @inrefs = map { sub {
312 24 100 100 24   54 return $_ unless $_->sv and
      66        
313             $_->sv->type eq "REF" and
314             $_->name eq "the referrant";
315              
316 4         64 my $rv = $_->sv;
317             my @rvrefs = $opts{strong} ? $rv->inrefs_strong :
318 4 0       55 $opts{direct} ? $rv->inrefs_direct :
    50          
319             $rv->inrefs;
320              
321 4 50       12 return $_ unless @rvrefs == 1;
322              
323             # Add 'via RV' marker
324             return map {
325 4         21 Devel::MAT::SV::Reference( Devel::MAT::Cmd->format_note( "(via RV)" ) . " " . $_->name,
  4         23  
326             $_->strength, $_->sv )
327             } @rvrefs;
328 24         272 }->() } @inrefs;
329             }
330              
331 3384 100       7593 if( $elide_pad ) {
332 14         19 @inrefs = map { sub {
333 24 100 100 24   48 return $_ unless $_->sv and
334             $_->sv->type eq "PAD";
335 1         13 my $pad = $_->sv;
336 1         5 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     10 my ( $depth ) = grep { $cv->pad( $_ ) == $pad } ( 1 .. ( $cv->depth || 1 ) );
  1         6  
340 1         4 return Devel::MAT::SV::Reference( $_->name . " at depth $depth", $_->strength, $cv );
341 24         309 }->() } @inrefs;
342             }
343              
344 3384     16844   39446 foreach my $ref ( sort_by { $_->name } @inrefs ) {
  16844         113192  
345 10144 100       92710 if( !defined $ref->sv ) {
346 4         58 $graph->add_root( $sv, $ref );
347 4         13 next;
348             }
349              
350 10140 100 100     69389 if( defined $opts{depth} and not $opts{depth} ) {
351 1679         12830 $graph->add_root( $sv, "EDEPTH" );
352 1679         4968 last;
353             }
354              
355 8461         10200 my @me;
356 8461 100       13155 if( $graph->has_sv( $ref->sv ) ) {
357 5078         9078 $graph->add_ref( $ref->sv, $sv, $ref );
358             # Don't recurse into it as it was already found
359             }
360             else {
361 3383         9890 $graph->add_sv( $ref->sv ); # add first to stop inf. loops
362              
363 3383 100       10640 defined $opts{depth} ? $self->inref_graph( $ref->sv, %opts, depth => $opts{depth}-1 )
364             : $self->inref_graph( $ref->sv, %opts );
365 3383         14080 $graph->add_ref( $ref->sv, $sv, $ref );
366             }
367             }
368              
369 3384         20181 return $graph->get_sv_node( $sv );
370             }
371              
372             =head2 find_symbol
373              
374             $sv = $pmat->find_symbol( $name )
375              
376             Attempts to walk the symbol table looking for a symbol of the given name,
377             which must include the sigil.
378              
379             $Package::Name::symbol_name => to return a SCALAR SV
380             @Package::Name::symbol_name => to return an ARRAY SV
381             %Package::Name::symbol_name => to return a HASH SV
382             &Package::Name::symbol_name => to return a CODE SV
383              
384             =cut
385              
386             sub find_symbol
387             {
388 30     30 1 3808 my $self = shift;
389 30         60 my ( $name ) = @_;
390              
391 30 50       239 my ( $sigil, $globname ) = $name =~ m/^([\$\@%&])(.*)$/ or
392             croak "Could not parse sigil from $name";
393              
394 30         91 my $stashvalue = $self->find_stashvalue( $globname );
395              
396             # Perl 5.22 may take CODE shortcuts
397 30 50 66     125 if( $sigil eq '&' and $stashvalue->type eq "REF" ) {
398 0         0 return $stashvalue->rv;
399             }
400              
401 30 50       102 $stashvalue->type eq "GLOB" or
402             croak "$globname is not a GLOB";
403              
404 30 50       109 my $slot = ( $sigil eq '$' ) ? "scalar" :
    100          
    100          
    100          
405             ( $sigil eq '@' ) ? "array" :
406             ( $sigil eq '%' ) ? "hash" :
407             ( $sigil eq '&' ) ? "code" :
408             die "ARGH"; # won't happen
409              
410 30 100       118 my $sv = $stashvalue->$slot or
411             croak "\*$globname has no $slot slot";
412 21         103 return $sv;
413             }
414              
415             =head2 find_glob
416              
417             $gv = $pmat->find_glob( $name )
418              
419             Attempts to walk the symbol table looking for a symbol of the given name,
420             returning the C object if found.
421              
422             =head2 find_stash
423              
424             $stash = $pmat->find_stash( $name )
425              
426             Attempts to walk the symbol table looking for a stash of the given name.
427              
428             =cut
429              
430             sub find_stashvalue
431             {
432 50     50 0 66 my $self = shift;
433 50         78 my ( $name ) = @_;
434              
435 50         371 my ( $parent, $shortname ) = $name =~ m/^(?:(.*)::)?(.+?)$/;
436              
437 50         83 my $stash;
438 50 100 100     172 if( defined $parent and length $parent ) {
439 14         35 $stash = $self->find_stash( $parent );
440             }
441             else {
442 36         125 $stash = $self->dumpfile->defstash;
443             }
444              
445 50 50       211 my $sv = $stash->value( $shortname ) or
446             croak $stash->stashname . " has no symbol $shortname";
447 50         113 return $sv;
448             }
449              
450             sub find_glob
451             {
452 20     20 1 2568 my $self = shift;
453 20         32 my ( $name ) = @_;
454              
455 20 50       51 my $sv = $self->find_stashvalue( $name ) or return;
456 20 50       92 $sv->type eq "GLOB" or
457             croak "$name is not a GLOB";
458              
459 20         51 return $sv;
460             }
461              
462             sub find_stash
463             {
464 15     15 1 633 my $self = shift;
465 15         27 my ( $name ) = @_;
466              
467 15         46 my $gv = $self->find_glob( $name . "::" );
468 15   33     40 return $gv->hash ||
469             croak "$name has no hash";
470             }
471              
472             # Some base implementations of Devel::MAT::Cmd formatters
473              
474             push @Devel::MAT::Cmd::ISA, qw( Devel::MAT::Cmd::_base );
475              
476             package
477             Devel::MAT::Cmd::_base;
478              
479 9     9   17162 use B qw( perlstring );
  9         29  
  9         654  
480 9     9   53 use List::Util qw( max );
  9         15  
  9         13189  
481              
482             sub print_table
483             {
484 6     6   2974 my $self = shift;
485 6         11 my ( $rows, %opts ) = @_;
486              
487 6 100       12 if( $opts{headings} ) {
488 1         1 my @headings = map { $self->format_heading( $_ ) } @{ $opts{headings} };
  2         6  
  1         3  
489 1         5 $rows = [ \@headings, @$rows ];
490             }
491              
492 6 50       12 return unless @$rows;
493              
494 6         9 my $cols = max map { scalar @$_ } @$rows;
  9         18  
495              
496             my @colwidths = map {
497 6         14 my $colidx = $_;
  17         14  
498             # TODO: consider a unicode/terminal-aware version of length here
499 17   50     15 max map { length($_->[$colidx]) // 0 } @$rows;
  25         49  
500             } 0 .. $cols-1;
501              
502 6   100     19 my $align = $opts{align} // "";
503 6 100       15 $align = [ ( $align ) x $cols ] if !ref $align;
504              
505 6   100     15 my $sep = $opts{sep} // " ";
506 6 50       12 $sep = [ ( $sep ) x ($cols - 1) ] if !ref $sep;
507              
508 6   100     8 my @leftalign = map { ($align->[$_]//"") ne "right" } 0 .. $cols-1;
  17         30  
509              
510             my $format = join( "",
511             ( " " x ( $opts{indent} // 0 ) ),
512             ( map {
513 6   100     38 my $col = $_;
  17         17  
514 17         14 my $width = $colwidths[$col];
515 17 100       21 my $flags = $leftalign[$col] ? "-" : "";
516             # If final column should be left-aligned don't bother with width
517 17 50 66     31 $width = "" if $col == $cols-1 and $leftalign[$col];
518              
519 17 100       44 ( $col ? $sep->[$col-1] : "" ) . "%${flags}${width}s"
520             } 0 .. $cols-1 ),
521             ) . "\n";
522              
523 6         22 foreach my $row ( @$rows ) {
524 9         34 my @row = @$row;
525 9 50       14 @row or @row = map { "-"x$colwidths[$_] } ( 0 .. $cols-1 );
  0         0  
526 9         14 push @row, "" while @row < $cols; # pad with spaces
527 9         16 $self->printf( $format, @row );
528             }
529             }
530              
531             sub format_note
532             {
533 111     111   122 shift;
534 111         174 my ( $str, $idx ) = @_;
535              
536 111         270 return $str;
537             }
538              
539             sub _format_sv
540             {
541 4     4   6 shift;
542 4         8 my ( $ret ) = @_;
543              
544 4         14 return $ret;
545             }
546              
547             sub format_sv
548             {
549 4     4   35 shift;
550 4         8 my ( $sv ) = @_;
551              
552 4         19 my $ret = $sv->desc;
553              
554 4 50       13 if( my $blessed = $sv->blessed ) {
555 0         0 $ret .= "=" . Devel::MAT::Cmd->format_symbol( $blessed->stashname, $blessed );
556             }
557              
558 4         20 $ret .= sprintf " at %#x", $sv->addr;
559              
560 4 100       20 if( my $rootname = $sv->rootname ) {
561 2         6 $ret .= "=" . Devel::MAT::Cmd->format_note( $rootname, 1 );
562             }
563              
564 4         19 return Devel::MAT::Cmd->_format_sv( $ret, $sv );
565             }
566              
567             sub _format_value
568             {
569 6548118     6548118   5959836 shift;
570 6548118         6906410 my ( $val ) = @_;
571              
572 6548118         18032819 return $val;
573             }
574              
575             sub format_value
576             {
577 6548118     6548118   5988066 shift;
578 6548118         9232638 my ( $val, %opts ) = @_;
579              
580 6548118         6065659 my $text;
581 6548118 100       9212135 if( $opts{key} ) {
    50          
    0          
582 2213205         2290302 my $strval = $val;
583 2213205 50 33     5873799 if( $opts{stash} && $strval =~ m/^([\x00-\x1f])([a-zA-Z0-9_]*)$/ ) {
    100          
584 0         0 $strval = "^" . chr( 64 + ord $1 ) . $2;
585             }
586             elsif( $strval !~ m/^[a-zA-Z_][a-zA-Z0-9_]*$/ ) {
587 460398         1258291 $strval = perlstring( $val );
588             }
589              
590 2213205         3088056 return "{" . Devel::MAT::Cmd->_format_value( $strval ) . "}";
591             }
592             elsif( $opts{index} ) {
593 4334913         5384079 return "[" . Devel::MAT::Cmd->_format_value( $val+0 ) . "]";
594             }
595             elsif( $opts{pv} ) {
596 0         0 my $truncated;
597 0 0 0     0 if( my $maxlen = $opts{maxlen} // 64 ) {
598 0 0       0 ( $truncated = length $val > $maxlen ) and
599             substr( $val, $maxlen ) = "";
600             }
601              
602 0 0       0 return Devel::MAT::Cmd->_format_value(
603             perlstring( $val ) . ( $truncated ? "..." : "" )
604             );
605             }
606             else {
607 0         0 return Devel::MAT::Cmd->_format_value( $val );
608             }
609             }
610              
611             sub format_symbol
612             {
613 1     1   2 shift;
614 1         12 my ( $name ) = @_;
615              
616 1         8 return $name;
617             }
618              
619             sub format_bytes
620             {
621 0     0     shift;
622 0           my ( $bytes ) = @_;
623              
624 0 0         if( $bytes < 1024 ) {
625 0           return sprintf "%d bytes", $bytes;
626             }
627 0 0         if( $bytes < 1024**2 ) {
628 0           return sprintf "%.1f KiB", $bytes / 1024;
629             }
630 0 0         if( $bytes < 1024**3 ) {
631 0           return sprintf "%.1f MiB", $bytes / 1024**2;
632             }
633 0 0         if( $bytes < 1024**4 ) {
634 0           return sprintf "%.1f GiB", $bytes / 1024**3;
635             }
636 0           return sprintf "%.1f TiB", $bytes / 1024**4;
637             }
638              
639             sub format_sv_with_value
640             {
641 0     0     my $self = shift;
642 0           my ( $sv ) = @_;
643              
644 0           my $repr = $self->format_sv( $sv );
645              
646             match( $sv->type : eq ) {
647             case( "SCALAR" ) {
648 0           my @reprs;
649              
650             my $num;
651 0 0 0       defined( $num = $sv->nv // $sv->uv ) and
652             push @reprs, $self->format_value( $num, nv => 1 );
653              
654 0 0         defined $sv->pv and
655             push @reprs, $self->format_value( $sv->pv, pv => 1 );
656              
657             # Dualvars
658 0 0         return "$repr = $reprs[0] / $reprs[1]" if @reprs > 1;
659              
660 0 0         return "$repr = $reprs[0]" if @reprs;
661             }
662             case( "BOOL" ) {
663 0 0         return "$repr = " . $self->format_value( $sv->uv ? "true" : "false" );
664             }
665             case( "REF" ) {
666             #return "REF => NULL" if !$sv->rv;
667 0 0         return "$repr => " . $self->format_sv_with_value( $sv->rv ) if $sv->rv;
668             }
669             case( "ARRAY" ) {
670 0 0         return $repr if $sv->blessed;
671              
672 0           my $n_elems = $sv->elems;
673 0 0         return "$repr = []" if !$n_elems;
674              
675 0           my $elem = $self->format_sv( $sv->elem( 0 ) );
676 0 0         $elem .= ", ..." if $n_elems > 1;
677              
678 0           return "$repr = [$elem]";
679             }
680             case( "HASH" ) {
681 0 0         return $repr if $sv->blessed;
682              
683 0           my $n_values = $sv->values;
684 0 0         return "$repr = {}" if !$n_values;
685              
686 0           my $key = ( $sv->keys )[0]; # pick one at random
687 0           my $value = $self->format_value( $key, key => 1 ) . " => " . $self->format_sv( $sv->value( $key ) );
688 0 0         $value .= ", ..." if $n_values > 1;
689              
690 0           return "$repr = {$value}";
691             }
692             case( "GLOB" ) {
693 0           return "$repr is " . $self->format_symbol( "*" . $sv->stashname, $sv );
694             }
695 0 0         case( "STASH" ) {
    0          
    0          
    0          
    0          
    0          
    0          
696 0           return "$repr is " . $self->format_symbol( $sv->stashname, $sv );
697             }
698             }
699              
700 0           return $repr;
701             }
702              
703             sub format_heading
704             {
705 0     0     shift;
706 0           my ( $text, $level ) = @_;
707              
708 0           return "$text";
709             }
710              
711             =head1 AUTHOR
712              
713             Paul Evans
714              
715             =cut
716              
717             0x55AA;