1require 5.014; # For more reliable $@ after eval 2package dumpvar; 3 4# Needed for PrettyPrinter only: 5 6# require 5.001; # Well, it coredumps anyway undef DB in 5.000 (not now) 7 8# translate control chars to ^X - Randal Schwartz 9# Modifications to print types by Peter Gordon v1.0 10 11# Ilya Zakharevich -- patches after 5.001 (and some before ;-) 12 13# Won't dump symbol tables and contents of debugged files by default 14 15$winsize = 80 unless defined $winsize; 16 17sub ASCII { return ord('A') == 65; } 18 19 20# Defaults 21 22# $globPrint = 1; 23$printUndef = 1 unless defined $printUndef; 24$tick = "auto" unless defined $tick; 25$unctrl = 'quote' unless defined $unctrl; 26$subdump = 1; 27$dumpReused = 0 unless defined $dumpReused; 28$bareStringify = 1 unless defined $bareStringify; 29 30my $APC = chr utf8::unicode_to_native(0x9F); 31my $backslash_c_question = (ASCII) ? '\177' : $APC; 32 33sub main::dumpValue { 34 local %address; 35 local $^W=0; 36 (print "undef\n"), return unless defined $_[0]; 37 (print &stringify($_[0]), "\n"), return unless ref $_[0]; 38 push @_, -1 if @_ == 1; 39 dumpvar::unwrap($_[0], 0, $_[1]); 40} 41 42# This one is good for variable names: 43 44sub unctrl { 45 for (my($dummy) = shift) { 46 local($v) ; 47 48 return \$_ if ref \$_ eq "GLOB"; 49 s/([\000-\037])/ '^' . chr(utf8::unicode_to_native(ord($1)^64))/eg; 50 s/ $backslash_c_question /^?/xg; 51 return $_; 52 } 53} 54 55sub uniescape { 56 join("", 57 map { $_ > 255 ? sprintf("\\x{%04X}", $_) : chr($_) } 58 unpack("W*", $_[0])); 59} 60 61sub stringify { 62 my $string; 63 if (eval { $string = _stringify(@_); 1 }) { 64 return $string; 65 } 66 67 return "<< value could not be dumped: $@ >>"; 68} 69 70sub _stringify { 71 (my $__, local $noticks) = @_; 72 for ($__) { 73 local($v) ; 74 my $tick = $tick; 75 76 return 'undef' unless defined $_ or not $printUndef; 77 return $_ . "" if ref \$_ eq 'GLOB'; 78 $_ = &{'overload::StrVal'}($_) 79 if $bareStringify and ref $_ 80 and %overload:: and defined &{'overload::StrVal'}; 81 82 if ($tick eq 'auto') { 83 if (/[^[:^cntrl:]\n]/u) { # All controls but \n get '"' 84 $tick = '"'; 85 } else { 86 $tick = "'"; 87 } 88 } 89 if ($tick eq "'") { 90 s/([\'\\])/\\$1/g; 91 } elsif ($unctrl eq 'unctrl') { 92 s/([\"\\])/\\$1/g ; 93 $_ = &unctrl($_); 94 # uniescape? 95 s/([[:^ascii:]])/'\\0x'.sprintf('%2X',ord($1))/eg 96 if $quoteHighBit; 97 } elsif ($unctrl eq 'quote') { 98 s/([\"\\\$\@])/\\$1/g if $tick eq '"'; 99 s/\e/\\e/g; 100 s/([\000-\037$backslash_c_question])/'\\c'._escaped_ord($1)/eg; 101 } 102 $_ = uniescape($_); 103 s/([[:^ascii:]])/'\\'.sprintf('%3o',ord($1))/eg if $quoteHighBit; 104 return ($noticks || /^\d+(\.\d*)?\Z/) 105 ? $_ 106 : $tick . $_ . $tick; 107 } 108} 109 110# Ensure a resulting \ is escaped to be \\ 111sub _escaped_ord { 112 my $chr = shift; 113 if ($chr eq $backslash_c_question) { 114 $chr = '?'; 115 } 116 else { 117 $chr = chr(utf8::unicode_to_native(ord($chr)^64)); 118 $chr =~ s{\\}{\\\\}g; 119 } 120 return $chr; 121} 122 123sub ShortArray { 124 my $tArrayDepth = $#{$_[0]} ; 125 $tArrayDepth = $#{$_[0]} < $arrayDepth-1 ? $#{$_[0]} : $arrayDepth-1 126 unless $arrayDepth eq '' ; 127 my $shortmore = ""; 128 $shortmore = " ..." if $tArrayDepth < $#{$_[0]} ; 129 if (!grep(ref $_, @{$_[0]})) { 130 $short = "0..$#{$_[0]} '" . 131 join("' '", @{$_[0]}[0..$tArrayDepth]) . "'$shortmore"; 132 return $short if length $short <= $compactDump; 133 } 134 undef; 135} 136 137sub DumpElem { 138 my $short = &stringify($_[0], ref $_[0]); 139 if ($veryCompact && ref $_[0] 140 && (ref $_[0] eq 'ARRAY' and !grep(ref $_, @{$_[0]}) )) { 141 my $end = "0..$#{$v} '" . 142 join("' '", @{$_[0]}[0..$tArrayDepth]) . "'$shortmore"; 143 } elsif ($veryCompact && ref $_[0] 144 && (ref $_[0] eq 'HASH') and !grep(ref $_, values %{$_[0]})) { 145 my $end = 1; 146 $short = $sp . "0..$#{$v} '" . 147 join("' '", @{$v}[0..$tArrayDepth]) . "'$shortmore"; 148 } else { 149 print "$short\n"; 150 unwrap($_[0],$_[1],$_[2]) if ref $_[0]; 151 } 152} 153 154sub unwrap { 155 return if $DB::signal; 156 local($v) = shift ; 157 local($s) = shift ; # extra no of spaces 158 local($m) = shift ; # maximum recursion depth 159 return if $m == 0; 160 local(%v,@v,$sp,$value,$key,@sortKeys,$more,$shortmore,$short) ; 161 local($tHashDepth,$tArrayDepth) ; 162 163 $sp = " " x $s ; 164 $s += 3 ; 165 166 eval { 167 # Check for reused addresses 168 if (ref $v) { 169 my $val = $v; 170 $val = &{'overload::StrVal'}($v) 171 if %overload:: and defined &{'overload::StrVal'}; 172 # Match type and address. 173 # Unblessed references will look like TYPE(0x...) 174 # Blessed references will look like Class=TYPE(0x...) 175 $val =~ s/^.*=//; # suppress the Class part, just keep TYPE(0x...) 176 ($item_type, $address) = 177 $val =~ /([^\(]+) # Keep stuff that's 178 # not an open paren 179 \( # Skip open paren 180 (0x[0-9a-f]+) # Save the address 181 \) # Skip close paren 182 $/x; # Should be at end now 183 184 if (!$dumpReused && defined $address) { 185 $address{$address}++ ; 186 if ( $address{$address} > 1 ) { 187 print "${sp}-> REUSED_ADDRESS\n" ; 188 return ; 189 } 190 } 191 } elsif (ref \$v eq 'GLOB') { 192 # This is a raw glob. Special handling for that. 193 $address = "$v" . ""; # To avoid a bug with globs 194 $address{$address}++ ; 195 if ( $address{$address} > 1 ) { 196 print "${sp}*DUMPED_GLOB*\n" ; 197 return ; 198 } 199 } 200 201 if (ref $v eq 'Regexp') { 202 # Reformat the regexp to look the standard way. 203 my $re = "$v"; 204 $re =~ s,/,\\/,g; 205 print "$sp-> qr/$re/\n"; 206 return; 207 } 208 209 if ( $item_type eq 'HASH' ) { 210 # Hash ref or hash-based object. 211 my @sortKeys = sort keys(%$v) ; 212 undef $more ; 213 $tHashDepth = $#sortKeys ; 214 $tHashDepth = $#sortKeys < $hashDepth-1 ? $#sortKeys : $hashDepth-1 215 unless $hashDepth eq '' ; 216 $more = "....\n" if $tHashDepth < $#sortKeys ; 217 $shortmore = ""; 218 $shortmore = ", ..." if $tHashDepth < $#sortKeys ; 219 $#sortKeys = $tHashDepth ; 220 if ($compactDump && !grep(ref $_, values %{$v})) { 221 #$short = $sp . 222 # (join ', ', 223# Next row core dumps during require from DB on 5.000, even with map {"_"} 224 # map {&stringify($_) . " => " . &stringify($v->{$_})} 225 # @sortKeys) . "'$shortmore"; 226 $short = $sp; 227 my @keys; 228 for (@sortKeys) { 229 push @keys, &stringify($_) . " => " . &stringify($v->{$_}); 230 } 231 $short .= join ', ', @keys; 232 $short .= $shortmore; 233 (print "$short\n"), return if length $short <= $compactDump; 234 } 235 for $key (@sortKeys) { 236 return if $DB::signal; 237 $value = $ {$v}{$key} ; 238 print "$sp", &stringify($key), " => "; 239 DumpElem $value, $s, $m-1; 240 } 241 print "$sp empty hash\n" unless @sortKeys; 242 print "$sp$more" if defined $more ; 243 } elsif ( $item_type eq 'ARRAY' ) { 244 # Array ref or array-based object. Also: undef. 245 # See how big the array is. 246 $tArrayDepth = $#{$v} ; 247 undef $more ; 248 # Bigger than the max? 249 $tArrayDepth = $#{$v} < $arrayDepth-1 ? $#{$v} : $arrayDepth-1 250 if defined $arrayDepth && $arrayDepth ne ''; 251 # Yep. Don't show it all. 252 $more = "....\n" if $tArrayDepth < $#{$v} ; 253 $shortmore = ""; 254 $shortmore = " ..." if $tArrayDepth < $#{$v} ; 255 256 if ($compactDump && !grep(ref $_, @{$v})) { 257 if ($#$v >= 0) { 258 $short = $sp . "0..$#{$v} " . 259 join(" ", 260 map {exists $v->[$_] ? stringify $v->[$_] : "empty"} (0..$tArrayDepth) 261 ) . "$shortmore"; 262 } else { 263 $short = $sp . "empty array"; 264 } 265 (print "$short\n"), return if length $short <= $compactDump; 266 } 267 #if ($compactDump && $short = ShortArray($v)) { 268 # print "$short\n"; 269 # return; 270 #} 271 for $num (0 .. $tArrayDepth) { 272 return if $DB::signal; 273 print "$sp$num "; 274 if (exists $v->[$num]) { 275 if (defined $v->[$num]) { 276 DumpElem $v->[$num], $s, $m-1; 277 } 278 else { 279 print "undef\n"; 280 } 281 } else { 282 print "empty slot\n"; 283 } 284 } 285 print "$sp empty array\n" unless @$v; 286 print "$sp$more" if defined $more ; 287 } elsif ( $item_type eq 'SCALAR' ) { 288 unless (defined $$v) { 289 print "$sp-> undef\n"; 290 return; 291 } 292 print "$sp-> "; 293 DumpElem $$v, $s, $m-1; 294 } elsif ( $item_type eq 'REF' ) { 295 print "$sp-> $$v\n"; 296 return unless defined $$v; 297 unwrap($$v, $s+3, $m-1); 298 } elsif ( $item_type eq 'CODE' ) { 299 # Code object or reference. 300 print "$sp-> "; 301 dumpsub (0, $v); 302 } elsif ( $item_type eq 'GLOB' ) { 303 # Glob object or reference. 304 print "$sp-> ",&stringify($$v,1),"\n"; 305 if ($globPrint) { 306 $s += 3; 307 dumpglob($s, "{$$v}", $$v, 1, $m-1); 308 } elsif (defined ($fileno = eval {fileno($v)})) { 309 print( (' ' x ($s+3)) . "FileHandle({$$v}) => fileno($fileno)\n" ); 310 } 311 } elsif (ref \$v eq 'GLOB') { 312 # Raw glob (again?) 313 if ($globPrint) { 314 dumpglob($s, "{$v}", $v, 1, $m-1) if $globPrint; 315 } elsif (defined ($fileno = eval {fileno(\$v)})) { 316 print( (' ' x $s) . "FileHandle({$v}) => fileno($fileno)\n" ); 317 } 318 } 319 }; 320 if ($@) { 321 print( (' ' x $s) . "<< value could not be dumped: $@ >>\n"); 322 } 323 324 return; 325} 326 327sub matchlex { 328 (my $var = $_[0]) =~ s/.//; 329 $var eq $_[1] or 330 ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and 331 ($1 eq '!') ^ (eval { $var =~ /$2$3/ }); 332} 333 334sub matchvar { 335 $_[0] eq $_[1] or 336 ($_[1] =~ /^([!~])(.)([\x00-\xff]*)/) and 337 ($1 eq '!') ^ (eval {($_[2] . "::" . $_[0]) =~ /$2$3/}); 338} 339 340sub compactDump { 341 $compactDump = shift if @_; 342 $compactDump = 6*80-1 if $compactDump and $compactDump < 2; 343 $compactDump; 344} 345 346sub veryCompact { 347 $veryCompact = shift if @_; 348 compactDump(1) if !$compactDump and $veryCompact; 349 $veryCompact; 350} 351 352sub unctrlSet { 353 if (@_) { 354 my $in = shift; 355 if ($in eq 'unctrl' or $in eq 'quote') { 356 $unctrl = $in; 357 } else { 358 print "Unknown value for 'unctrl'.\n"; 359 } 360 } 361 $unctrl; 362} 363 364sub quote { 365 if (@_ and $_[0] eq '"') { 366 $tick = '"'; 367 $unctrl = 'quote'; 368 } elsif (@_ and $_[0] eq 'auto') { 369 $tick = 'auto'; 370 $unctrl = 'quote'; 371 } elsif (@_) { # Need to set 372 $tick = "'"; 373 $unctrl = 'unctrl'; 374 } 375 $tick; 376} 377 378sub dumpglob { 379 return if $DB::signal; 380 my ($off,$key, $val, $all, $m) = @_; 381 local(*entry) = $val; 382 my $fileno; 383 if (($key !~ /^_</ or $dumpDBFiles) and defined $entry) { 384 print( (' ' x $off) . "\$", &unctrl($key), " = " ); 385 DumpElem $entry, 3+$off, $m; 386 } 387 if (($key !~ /^_</ or $dumpDBFiles) and @entry) { 388 print( (' ' x $off) . "\@$key = (\n" ); 389 unwrap(\@entry,3+$off,$m) ; 390 print( (' ' x $off) . ")\n" ); 391 } 392 if ($key ne "main::" && $key ne "DB::" && %entry 393 && ($dumpPackages or $key !~ /::$/) 394 && ($key !~ /^_</ or $dumpDBFiles) 395 && !($package eq "dumpvar" and $key eq "stab")) { 396 print( (' ' x $off) . "\%$key = (\n" ); 397 unwrap(\%entry,3+$off,$m) ; 398 print( (' ' x $off) . ")\n" ); 399 } 400 if (defined ($fileno = eval{fileno(*entry)})) { 401 print( (' ' x $off) . "FileHandle($key) => fileno($fileno)\n" ); 402 } 403 if ($all) { 404 if (defined &entry) { 405 dumpsub($off, $key); 406 } 407 } 408} 409 410sub dumplex { 411 return if $DB::signal; 412 my ($key, $val, $m, @vars) = @_; 413 return if @vars && !grep( matchlex($key, $_), @vars ); 414 local %address; 415 my $off = 0; # It reads better this way 416 my $fileno; 417 if (UNIVERSAL::isa($val,'ARRAY')) { 418 print( (' ' x $off) . "$key = (\n" ); 419 unwrap($val,3+$off,$m) ; 420 print( (' ' x $off) . ")\n" ); 421 } 422 elsif (UNIVERSAL::isa($val,'HASH')) { 423 print( (' ' x $off) . "$key = (\n" ); 424 unwrap($val,3+$off,$m) ; 425 print( (' ' x $off) . ")\n" ); 426 } 427 elsif (UNIVERSAL::isa($val,'IO')) { 428 print( (' ' x $off) . "FileHandle($key) => fileno($fileno)\n" ); 429 } 430 # No lexical subroutines yet... 431 # elsif (UNIVERSAL::isa($val,'CODE')) { 432 # dumpsub($off, $$val); 433 # } 434 else { 435 print( (' ' x $off) . &unctrl($key), " = " ); 436 DumpElem $$val, 3+$off, $m; 437 } 438} 439 440sub CvGV_name_or_bust { 441 my $in = shift; 442 return if $skipCvGV; # Backdoor to avoid problems if XS broken... 443 $in = \&$in; # Hard reference... 444 eval {require Devel::Peek; 1} or return; 445 my $gv = Devel::Peek::CvGV($in) or return; 446 *$gv{PACKAGE} . '::' . *$gv{NAME}; 447} 448 449sub dumpsub { 450 my ($off,$sub) = @_; 451 my $ini = $sub; 452 my $s; 453 $sub = $1 if $sub =~ /^\{\*(.*)\}$/; 454 my $subref = defined $1 ? \&$sub : \&$ini; 455 my $place = $DB::sub{$sub} || (($s = $subs{"$subref"}) && $DB::sub{$s}) 456 || (($s = CvGV_name_or_bust($subref)) && $DB::sub{$s}) 457 || ($subdump && ($s = findsubs("$subref")) && $DB::sub{$s}); 458 $place = '???' unless defined $place; 459 $s = $sub unless defined $s; 460 print( (' ' x $off) . "&$s in $place\n" ); 461} 462 463sub findsubs { 464 return undef unless %DB::sub; 465 my ($addr, $name, $loc); 466 while (($name, $loc) = each %DB::sub) { 467 $addr = \&$name; 468 $subs{"$addr"} = $name; 469 } 470 $subdump = 0; 471 $subs{ shift() }; 472} 473 474sub main::dumpvar { 475 my ($package,$m,@vars) = @_; 476 local(%address,$key,$val,$^W); 477 $package .= "::" unless $package =~ /::$/; 478 *stab = *{"main::"}; 479 while ($package =~ /(\w+?::)/g){ 480 *stab = $ {stab}{$1}; 481 } 482 local $TotalStrings = 0; 483 local $Strings = 0; 484 local $CompleteTotal = 0; 485 while (($key,$val) = each(%stab)) { 486 return if $DB::signal; 487 next if @vars && !grep( matchvar($key, $_), @vars ); 488 if ($usageOnly) { 489 globUsage(\$val, $key) 490 if ($package ne 'dumpvar' or $key ne 'stab') 491 and ref(\$val) eq 'GLOB'; 492 } else { 493 dumpglob(0,$key, $val, 0, $m); 494 } 495 } 496 if ($usageOnly) { 497 print "String space: $TotalStrings bytes in $Strings strings.\n"; 498 $CompleteTotal += $TotalStrings; 499 print "Grand total = $CompleteTotal bytes (1 level deep) + overhead.\n"; 500 } 501} 502 503sub scalarUsage { 504 my $size = length($_[0]); 505 $TotalStrings += $size; 506 $Strings++; 507 $size; 508} 509 510sub arrayUsage { # array ref, name 511 my $size = 0; 512 map {$size += scalarUsage($_)} @{$_[0]}; 513 my $len = @{$_[0]}; 514 print "\@$_[1] = $len item", ($len > 1 ? "s" : ""), 515 " (data: $size bytes)\n" 516 if defined $_[1]; 517 $CompleteTotal += $size; 518 $size; 519} 520 521sub hashUsage { # hash ref, name 522 my @keys = keys %{$_[0]}; 523 my @values = values %{$_[0]}; 524 my $keys = arrayUsage \@keys; 525 my $values = arrayUsage \@values; 526 my $len = @keys; 527 my $total = $keys + $values; 528 print "\%$_[1] = $len item", ($len > 1 ? "s" : ""), 529 " (keys: $keys; values: $values; total: $total bytes)\n" 530 if defined $_[1]; 531 $total; 532} 533 534sub globUsage { # glob ref, name 535 local *name = *{$_[0]}; 536 $total = 0; 537 $total += scalarUsage $name if defined $name; 538 $total += arrayUsage \@name, $_[1] if @name; 539 $total += hashUsage \%name, $_[1] if %name and $_[1] ne "main::" 540 and $_[1] ne "DB::"; #and !($package eq "dumpvar" and $key eq "stab")); 541 $total; 542} 543 544sub packageUsage { 545 my ($package,@vars) = @_; 546 $package .= "::" unless $package =~ /::$/; 547 local *stab = *{"main::"}; 548 while ($package =~ /(\w+?::)/g){ 549 *stab = $ {stab}{$1}; 550 } 551 local $TotalStrings = 0; 552 local $CompleteTotal = 0; 553 my ($key,$val); 554 while (($key,$val) = each(%stab)) { 555 next if @vars && !grep($key eq $_,@vars); 556 globUsage \$val, $key unless $package eq 'dumpvar' and $key eq 'stab'; 557 } 558 print "String space: $TotalStrings.\n"; 559 $CompleteTotal += $TotalStrings; 560 print "\nGrand total = $CompleteTotal bytes\n"; 561} 562 5631; 564 565