This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
More updates to Module-CoreList for Perl 5.20.2
[perl5.git] / lib / diagnostics.pm
old mode 100755 (executable)
new mode 100644 (file)
index a191035..40c6748
@@ -59,13 +59,14 @@ descriptions).  User code generated warnings a la warn() are unaffected,
 allowing duplicate user messages to be displayed.
 
 This module also adds a stack trace to the error message when perl dies.
-This is useful for pinpointing what caused the death. The B<-traceonly> (or
-just B<-t>) flag turns off the explantions of warning messages leaving just
-the stack traces. So if your script is dieing, run it again with
+This is useful for pinpointing what
+caused the death.  The B<-traceonly> (or
+just B<-t>) flag turns off the explanations of warning messages leaving just
+the stack traces.  So if your script is dieing, run it again with
 
   perl -Mdiagnostics=-traceonly my_bad_script
 
-to see the call stack at the time of death. By supplying the B<-warntrace>
+to see the call stack at the time of death.  By supplying the B<-warntrace>
 (or just B<-w>) flag, any warnings emitted will also come with a stack
 trace.
 
@@ -181,11 +182,11 @@ Tom Christiansen <F<tchrist@mox.perl.com>>, 25 June 1995.
 =cut
 
 use strict;
-use 5.006;
+use 5.009001;
 use Carp;
 $Carp::Internal{__PACKAGE__.""}++;
 
-our $VERSION = 1.14;
+our $VERSION = '1.34';
 our $DEBUG;
 our $VERBOSE;
 our $PRETTY;
@@ -193,35 +194,25 @@ our $TRACEONLY = 0;
 our $WARNTRACE = 0;
 
 use Config;
-my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
+use Text::Tabs 'expand';
+my $privlib = $Config{privlibexp};
 if ($^O eq 'VMS') {
     require VMS::Filespec;
     $privlib = VMS::Filespec::unixify($privlib);
-    $archlib = VMS::Filespec::unixify($archlib);
 }
 my @trypod = (
-          "$archlib/pod/perldiag.pod",
-          "$privlib/pod/perldiag-$Config{version}.pod",
           "$privlib/pod/perldiag.pod",
-          "$archlib/pods/perldiag.pod",
-          "$privlib/pods/perldiag-$Config{version}.pod",
           "$privlib/pods/perldiag.pod",
          );
 # handy for development testing of new warnings etc
 unshift @trypod, "./pod/perldiag.pod" if -e "pod/perldiag.pod";
 (my $PODFILE) = ((grep { -e } @trypod), $trypod[$#trypod])[0];
 
-if ($^O eq 'MacOS') {
-    # just updir one from each lib dir, we'll find it ...
-    ($PODFILE) = grep { -e } map { "$_:pod:perldiag.pod" } @INC;
-}
-
-
 $DEBUG ||= 0;
-my $WHOAMI = ref bless [];  # nobody's business, prolly not even mine
 
 local $| = 1;
 local $_;
+local $.;
 
 my $standalone;
 my(%HTML_2_Troff, %HTML_2_Latin_1, %HTML_2_ASCII_7);
@@ -247,7 +238,7 @@ CONFIG: {
 
     if (caller) {
        INCPATH: {
-           for my $file ( (map { "$_/$WHOAMI.pm" } @INC), $0) {
+           for my $file ( (map { "$_/".__PACKAGE__.".pm" } @INC), $0) {
                warn "Checking $file\n" if $DEBUG;
                if (open(POD_DIAG, $file)) {
                    while (<POD_DIAG>) {
@@ -316,7 +307,6 @@ my %transfmt = ();
 my $transmo = <<EOFUNC;
 sub transmo {
     #local \$^W = 0;  # recursive warnings we do NOT need!
-    study;
 EOFUNC
 
 my %msg;
@@ -325,19 +315,48 @@ my %msg;
     local $/ = '';
     local $_;
     my $header;
+    my @headers;
     my $for_item;
+    my $seen_body;
     while (<POD_DIAG>) {
 
+       sub _split_pod_link {
+           $_[0] =~ m'(?:([^|]*)\|)?([^/]*)(?:/("?)(.*)\3)?'s;
+           ($1,$2,$4);
+       }
+
        unescape();
        if ($PRETTY) {
            sub noop   { return $_[0] }  # spensive for a noop
            sub bold   { my $str =$_[0];  $str =~ s/(.)/$1\b$1/g; return $str; } 
            sub italic { my $str = $_[0]; $str =~ s/(.)/_\b$1/g;  return $str; } 
            s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/bold($+)/ges;
-           s/[LIF]<(.*?)>/italic($1)/ges;
+           s/[IF]<(.*?)>/italic($1)/ges;
+           s/L<(.*?)>/
+              my($text,$page,$sect) = _split_pod_link($1);
+              defined $text
+               ? $text
+               : defined $sect
+                  ? italic($sect) . ' in ' . italic($page)
+                  : italic($page)
+            /ges;
+            s/S<(.*?)>/
+               $1
+             /ges;
        } else {
            s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/$+/gs;
-           s/[LIF]<(.*?)>/$1/gs;
+           s/[IF]<(.*?)>/$1/gs;
+           s/L<(.*?)>/
+              my($text,$page,$sect) = _split_pod_link($1);
+              defined $text
+               ? $text
+               : defined $sect
+                  ? qq '"$sect" in $page'
+                  : $page
+            /ges;
+           s/S<(.*?)>/
+               $1
+             /ges;
        } 
        unless (/^=/) {
            if (defined $header) { 
@@ -348,13 +367,26 @@ my %msg;
                {
                    next;
                }
+               $_ = expand $_;
                s/^/    /gm;
                $msg{$header} .= $_;
+               for my $h(@headers) { $msg{$h} .= $_ }
+               ++$seen_body;
                undef $for_item;        
            }
            next;
        } 
-       unless ( s/=item (.*?)\s*\z//) {
+
+       # If we have not come across the body of the description yet, then
+       # the previous header needs to share the same description.
+       if ($seen_body) {
+           @headers = ();
+       }
+       else {
+           push @headers, $header if defined $header;
+       }
+
+       unless ( s/=item (.*?)\s*\z//s) {
 
            if ( s/=head1\sDESCRIPTION//) {
                $msg{$header = 'DESCRIPTION'} = '';
@@ -362,57 +394,68 @@ my %msg;
            }
            elsif( s/^=for\s+diagnostics\s*\n(.*?)\s*\z// ) {
                $for_item = $1;
-           } 
+           }
+           elsif( /^=back/ ) { # Stop processing body here
+               undef $header;
+               undef $for_item;
+               $seen_body = 0;
+               next;
+           }
            next;
        }
 
        if( $for_item ) { $header = $for_item; undef $for_item } 
        else {
            $header = $1;
-           while( $header =~ /[;,]\z/ ) {
-               <POD_DIAG> =~ /^\s*(.*?)\s*\z/;
-               $header .= ' '.$1;
-           }
+
+           $header =~ s/\n/ /gs; # Allow multi-line headers
        }
 
        # strip formatting directives from =item line
        $header =~ s/[A-Z]<(.*?)>/$1/g;
 
-        my @toks = split( /(%l?[dx]|%c|%(?:\.\d+)?s)/, $header );
+       # Since we strip "(\.\s*)\n" when we search a warning, strip it here as well
+       $header =~ s/(\.\s*)?$//;
+
+        my @toks = split( /(%l?[dxX]|%[ucp]|%(?:\.\d+)?[fs])/, $header );
        if (@toks > 1) {
             my $conlen = 0;
             for my $i (0..$#toks){
                 if( $i % 2 ){
                     if(      $toks[$i] eq '%c' ){
                         $toks[$i] = '.';
-                    } elsif( $toks[$i] eq '%d' ){
+                    } elsif( $toks[$i] =~ /^%(?:d|u)$/ ){
                         $toks[$i] = '\d+';
-                    } elsif( $toks[$i] eq '%s' ){
+                    } elsif( $toks[$i] =~ '^%(?:s|.*f)$' ){
                         $toks[$i] = $i == $#toks ? '.*' : '.*?';
                     } elsif( $toks[$i] =~ '%.(\d+)s' ){
                         $toks[$i] = ".{$1}";
-                     } elsif( $toks[$i] =~ '^%l*x$' ){
-                        $toks[$i] = '[\da-f]+';
-                   }
+                    } elsif( $toks[$i] =~ '^%l*([pxX])$' ){
+                        $toks[$i] = $1 eq 'X' ? '[\dA-F]+' : '[\da-f]+';
+                    }
                 } elsif( length( $toks[$i] ) ){
-                    $toks[$i] =~ s/^.*$/\Q$&\E/;
+                    $toks[$i] = quotemeta $toks[$i];
                     $conlen += length( $toks[$i] );
                 }
             }  
             my $lhs = join( '', @toks );
+            $lhs =~ s/(\\\s)+/\\s+/g; # Replace lit space with multi-space match
            $transfmt{$header}{pat} =
-              "    s{^$lhs}\n     {\Q$header\E}s\n\t&& return 1;\n";
+              "    s\a^\\s*$lhs\\s*\a\Q$header\E\as\n\t&& return 1;\n";
             $transfmt{$header}{len} = $conlen;
        } else {
+            my $lhs = "\Q$header\E";
+            $lhs =~ s/(\\\s)+/\\s+/g; # Replace lit space with multi-space match
             $transfmt{$header}{pat} =
-             "    m{^\Q$header\E} && return 1;\n";
+             "    s\a^\\s*$lhs\\s*\a\Q$header\E\a\n\t && return 1;\n";
             $transfmt{$header}{len} = length( $header );
        } 
 
-       print STDERR "$WHOAMI: Duplicate entry: \"$header\"\n"
+       print STDERR __PACKAGE__.": Duplicate entry: \"$header\"\n"
            if $msg{$header};
 
        $msg{$header} = '';
+       $seen_body = 0;
     } 
 
 
@@ -466,12 +509,12 @@ sub import {
                                    $PRETTY++;
                                    next;
                               };
-
-       /^-t(race)?$/           && do {
+       # matches trace and traceonly for legacy doc mixup reasons
+       /^-t(race(only)?)?$/    && do {
                                    $TRACEONLY++;
                                    next;
                               };
-       /^-w(arntrace)?$/               && do {
+       /^-w(arntrace)?$/       && do {
                                    $WARNTRACE++;
                                    next;
                               };
@@ -496,7 +539,7 @@ sub disable {
 
 sub warn_trap {
     my $warning = $_[0];
-    if (caller eq $WHOAMI or !splainthis($warning)) {
+    if (caller eq __PACKAGE__ or !splainthis($warning)) {
        if ($WARNTRACE) {
            print STDERR Carp::longmess($warning);
        } else {
@@ -521,7 +564,9 @@ sub death_trap {
     }
 
     splainthis($exception) unless $in_eval;
-    if (caller eq $WHOAMI) { print STDERR "INTERNAL EXCEPTION: $exception"; } 
+    if (caller eq __PACKAGE__) {
+       print STDERR "INTERNAL EXCEPTION: $exception";
+    } 
     &$olddie if defined $olddie and $olddie and $olddie ne \&death_trap;
 
     return if $in_eval;
@@ -533,10 +578,12 @@ sub death_trap {
     # traps.
     $SIG{__DIE__} = $SIG{__WARN__} = '';
 
-    # Have carp skip over death_trap() when showing the stack trace.
-    local($Carp::CarpLevel) = 1;
+    $exception =~ s/\n(?=.)/\n\t/gas;
 
-    confess "Uncaught exception from user code:\n\t$exception";
+    die Carp::longmess("__diagnostics__")
+         =~ s/^__diagnostics__.*?line \d+\.?\n/
+                 "Uncaught exception from user code:\n\t$exception"
+             /re;
        # up we go; where we stop, nobody knows, but i think we die now
        # but i'm deeply afraid of the &$olddie guy reraising and us getting
        # into an indirect recursion loop
@@ -547,11 +594,12 @@ my %old_diag;
 my $count;
 my $wantspace;
 sub splainthis {
-    return 0 if $TRACEONLY;
-    local $_ = shift;
+  return 0 if $TRACEONLY;
+  for (my $tmp = shift) {
     local $\;
+    local $!;
     ### &finish_compilation unless %msg;
-    s/\.?\n+$//;
+    s/(\.\s*)?\n+$//;
     my $orig = $_;
     # return unless defined;
 
@@ -559,9 +607,10 @@ sub splainthis {
     s/, <.*?> (?:line|chunk).*$//;
 
     # Discard 1st " at <file> line <no>" and all text beyond
-    # but be aware of messsages containing " at this-or-that"
+    # but be aware of messages containing " at this-or-that"
     my $real = 0;
     my @secs = split( / at / );
+    return unless @secs;
     $_ = $secs[0];
     for my $i ( 1..$#secs ){
         if( $secs[$i] =~ /.+? (?:line|chunk) \d+/ ){
@@ -571,7 +620,7 @@ sub splainthis {
             $_ .= ' at ' . $secs[$i];
        }
     }
-    
+
     # remove parenthesis occurring at the end of some messages 
     s/^\((.*)\)$/$1/;
 
@@ -581,17 +630,25 @@ sub splainthis {
        return 0 unless &transmo;
     }
 
-    $orig = shorten($orig);
+    my $short = shorten($orig);
     if ($old_diag{$_}) {
        autodescribe();
-       print THITHER "$orig (#$old_diag{$_})\n";
+       print THITHER "$short (#$old_diag{$_})\n";
        $wantspace = 1;
+    } elsif (!$msg{$_} && $orig =~ /\n./s) {
+       # A multiline message, like "Attempt to reload /
+       # Compilation failed"
+       my $found;
+       for (split /^/, $orig) {
+           splainthis($_) and $found = 1;
+       }
+       return $found;
     } else {
        autodescribe();
        $old_diag{$_} = ++$count;
        print THITHER "\n" if $wantspace;
        $wantspace = 0;
-       print THITHER "$orig (#$old_diag{$_})\n";
+       print THITHER "$short (#$old_diag{$_})\n";
        if ($msg{$_}) {
            print THITHER $msg{$_};
        } else {
@@ -604,6 +661,7 @@ sub splainthis {
        } 
     }
     return 1;
+  }
 } 
 
 sub autodescribe {