ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/staticperl/perl/lib/diagnostics.pm
Revision: 1.1
Committed: Thu Jun 30 14:27:09 2005 UTC (21 years, 3 months ago) by root
Branch: MAIN
CVS Tags: PERL-5-8-7, HEAD
Branch point for: PERL
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 package diagnostics;
2    
3     =head1 NAME
4    
5     diagnostics, splain - produce verbose warning diagnostics
6    
7     =head1 SYNOPSIS
8    
9     Using the C<diagnostics> pragma:
10    
11     use diagnostics;
12     use diagnostics -verbose;
13    
14     enable diagnostics;
15     disable diagnostics;
16    
17     Using the C<splain> standalone filter program:
18    
19     perl program 2>diag.out
20     splain [-v] [-p] diag.out
21    
22     Using diagnostics to get stack traces from a misbehaving script:
23    
24     perl -Mdiagnostics=-traceonly my_script.pl
25    
26     =head1 DESCRIPTION
27    
28     =head2 The C<diagnostics> Pragma
29    
30     This module extends the terse diagnostics normally emitted by both the
31     perl compiler and the perl interpreter (from running perl with a -w
32     switch or C<use warnings>), augmenting them with the more
33     explicative and endearing descriptions found in L<perldiag>. Like the
34     other pragmata, it affects the compilation phase of your program rather
35     than merely the execution phase.
36    
37     To use in your program as a pragma, merely invoke
38    
39     use diagnostics;
40    
41     at the start (or near the start) of your program. (Note
42     that this I<does> enable perl's B<-w> flag.) Your whole
43     compilation will then be subject(ed :-) to the enhanced diagnostics.
44     These still go out B<STDERR>.
45    
46     Due to the interaction between runtime and compiletime issues,
47     and because it's probably not a very good idea anyway,
48     you may not use C<no diagnostics> to turn them off at compiletime.
49     However, you may control their behaviour at runtime using the
50     disable() and enable() methods to turn them off and on respectively.
51    
52     The B<-verbose> flag first prints out the L<perldiag> introduction before
53     any other diagnostics. The $diagnostics::PRETTY variable can generate nicer
54     escape sequences for pagers.
55    
56     Warnings dispatched from perl itself (or more accurately, those that match
57     descriptions found in L<perldiag>) are only displayed once (no duplicate
58     descriptions). User code generated warnings a la warn() are unaffected,
59     allowing duplicate user messages to be displayed.
60    
61     This module also adds a stack trace to the error message when perl dies.
62     This is useful for pinpointing what caused the death. The B<-traceonly> (or
63     just B<-t>) flag turns off the explantions of warning messages leaving just
64     the stack traces. So if your script is dieing, run it again with
65    
66     perl -Mdiagnostics=-traceonly my_bad_script
67    
68     to see the call stack at the time of death. By supplying the B<-warntrace>
69     (or just B<-w>) flag, any warnings emitted will also come with a stack
70     trace.
71    
72     =head2 The I<splain> Program
73    
74     While apparently a whole nuther program, I<splain> is actually nothing
75     more than a link to the (executable) F<diagnostics.pm> module, as well as
76     a link to the F<diagnostics.pod> documentation. The B<-v> flag is like
77     the C<use diagnostics -verbose> directive.
78     The B<-p> flag is like the
79     $diagnostics::PRETTY variable. Since you're post-processing with
80     I<splain>, there's no sense in being able to enable() or disable() processing.
81    
82     Output from I<splain> is directed to B<STDOUT>, unlike the pragma.
83    
84     =head1 EXAMPLES
85    
86     The following file is certain to trigger a few errors at both
87     runtime and compiletime:
88    
89     use diagnostics;
90     print NOWHERE "nothing\n";
91     print STDERR "\n\tThis message should be unadorned.\n";
92     warn "\tThis is a user warning";
93     print "\nDIAGNOSTIC TESTER: Please enter a <CR> here: ";
94     my $a, $b = scalar <STDIN>;
95     print "\n";
96     print $x/$y;
97    
98     If you prefer to run your program first and look at its problem
99     afterwards, do this:
100    
101     perl -w test.pl 2>test.out
102     ./splain < test.out
103    
104     Note that this is not in general possible in shells of more dubious heritage,
105     as the theoretical
106    
107     (perl -w test.pl >/dev/tty) >& test.out
108     ./splain < test.out
109    
110     Because you just moved the existing B<stdout> to somewhere else.
111    
112     If you don't want to modify your source code, but still have on-the-fly
113     warnings, do this:
114    
115     exec 3>&1; perl -w test.pl 2>&1 1>&3 3>&- | splain 1>&2 3>&-
116    
117     Nifty, eh?
118    
119     If you want to control warnings on the fly, do something like this.
120     Make sure you do the C<use> first, or you won't be able to get
121     at the enable() or disable() methods.
122    
123     use diagnostics; # checks entire compilation phase
124     print "\ntime for 1st bogus diags: SQUAWKINGS\n";
125     print BOGUS1 'nada';
126     print "done with 1st bogus\n";
127    
128     disable diagnostics; # only turns off runtime warnings
129     print "\ntime for 2nd bogus: (squelched)\n";
130     print BOGUS2 'nada';
131     print "done with 2nd bogus\n";
132    
133     enable diagnostics; # turns back on runtime warnings
134     print "\ntime for 3rd bogus: SQUAWKINGS\n";
135     print BOGUS3 'nada';
136     print "done with 3rd bogus\n";
137    
138     disable diagnostics;
139     print "\ntime for 4th bogus: (squelched)\n";
140     print BOGUS4 'nada';
141     print "done with 4th bogus\n";
142    
143     =head1 INTERNALS
144    
145     Diagnostic messages derive from the F<perldiag.pod> file when available at
146     runtime. Otherwise, they may be embedded in the file itself when the
147     splain package is built. See the F<Makefile> for details.
148    
149     If an extant $SIG{__WARN__} handler is discovered, it will continue
150     to be honored, but only after the diagnostics::splainthis() function
151     (the module's $SIG{__WARN__} interceptor) has had its way with your
152     warnings.
153    
154     There is a $diagnostics::DEBUG variable you may set if you're desperately
155     curious what sorts of things are being intercepted.
156    
157     BEGIN { $diagnostics::DEBUG = 1 }
158    
159    
160     =head1 BUGS
161    
162     Not being able to say "no diagnostics" is annoying, but may not be
163     insurmountable.
164    
165     The C<-pretty> directive is called too late to affect matters.
166     You have to do this instead, and I<before> you load the module.
167    
168     BEGIN { $diagnostics::PRETTY = 1 }
169    
170     I could start up faster by delaying compilation until it should be
171     needed, but this gets a "panic: top_level" when using the pragma form
172     in Perl 5.001e.
173    
174     While it's true that this documentation is somewhat subserious, if you use
175     a program named I<splain>, you should expect a bit of whimsy.
176    
177     =head1 AUTHOR
178    
179     Tom Christiansen <F<tchrist@mox.perl.com>>, 25 June 1995.
180    
181     =cut
182    
183     use strict;
184     use 5.006;
185     use Carp;
186     $Carp::Internal{__PACKAGE__.""}++;
187    
188     our $VERSION = 1.14;
189     our $DEBUG;
190     our $VERBOSE;
191     our $PRETTY;
192     our $TRACEONLY = 0;
193     our $WARNTRACE = 0;
194    
195     use Config;
196     my($privlib, $archlib) = @Config{qw(privlibexp archlibexp)};
197     if ($^O eq 'VMS') {
198     require VMS::Filespec;
199     $privlib = VMS::Filespec::unixify($privlib);
200     $archlib = VMS::Filespec::unixify($archlib);
201     }
202     my @trypod = (
203     "$archlib/pod/perldiag.pod",
204     "$privlib/pod/perldiag-$Config{version}.pod",
205     "$privlib/pod/perldiag.pod",
206     "$archlib/pods/perldiag.pod",
207     "$privlib/pods/perldiag-$Config{version}.pod",
208     "$privlib/pods/perldiag.pod",
209     );
210     # handy for development testing of new warnings etc
211     unshift @trypod, "./pod/perldiag.pod" if -e "pod/perldiag.pod";
212     (my $PODFILE) = ((grep { -e } @trypod), $trypod[$#trypod])[0];
213    
214     if ($^O eq 'MacOS') {
215     # just updir one from each lib dir, we'll find it ...
216     ($PODFILE) = grep { -e } map { "$_:pod:perldiag.pod" } @INC;
217     }
218    
219    
220     $DEBUG ||= 0;
221     my $WHOAMI = ref bless []; # nobody's business, prolly not even mine
222    
223     local $| = 1;
224     local $_;
225    
226     my $standalone;
227     my(%HTML_2_Troff, %HTML_2_Latin_1, %HTML_2_ASCII_7);
228    
229     CONFIG: {
230     our $opt_p = our $opt_d = our $opt_v = our $opt_f = '';
231    
232     unless (caller) {
233     $standalone++;
234     require Getopt::Std;
235     Getopt::Std::getopts('pdvf:')
236     or die "Usage: $0 [-v] [-p] [-f splainpod]";
237     $PODFILE = $opt_f if $opt_f;
238     $DEBUG = 2 if $opt_d;
239     $VERBOSE = $opt_v;
240     $PRETTY = $opt_p;
241     }
242    
243     if (open(POD_DIAG, $PODFILE)) {
244     warn "Happy happy podfile from real $PODFILE\n" if $DEBUG;
245     last CONFIG;
246     }
247    
248     if (caller) {
249     INCPATH: {
250     for my $file ( (map { "$_/$WHOAMI.pm" } @INC), $0) {
251     warn "Checking $file\n" if $DEBUG;
252     if (open(POD_DIAG, $file)) {
253     while (<POD_DIAG>) {
254     next unless
255     /^__END__\s*# wish diag dbase were more accessible/;
256     print STDERR "podfile is $file\n" if $DEBUG;
257     last INCPATH;
258     }
259     }
260     }
261     }
262     } else {
263     print STDERR "podfile is <DATA>\n" if $DEBUG;
264     *POD_DIAG = *main::DATA;
265     }
266     }
267     if (eof(POD_DIAG)) {
268     die "couldn't find diagnostic data in $PODFILE @INC $0";
269     }
270    
271    
272     %HTML_2_Troff = (
273     'amp' => '&', # ampersand
274     'lt' => '<', # left chevron, less-than
275     'gt' => '>', # right chevron, greater-than
276     'quot' => '"', # double quote
277    
278     "Aacute" => "A\\*'", # capital A, acute accent
279     # etc
280    
281     );
282    
283     %HTML_2_Latin_1 = (
284     'amp' => '&', # ampersand
285     'lt' => '<', # left chevron, less-than
286     'gt' => '>', # right chevron, greater-than
287     'quot' => '"', # double quote
288    
289     "Aacute" => "\xC1" # capital A, acute accent
290    
291     # etc
292     );
293    
294     %HTML_2_ASCII_7 = (
295     'amp' => '&', # ampersand
296     'lt' => '<', # left chevron, less-than
297     'gt' => '>', # right chevron, greater-than
298     'quot' => '"', # double quote
299    
300     "Aacute" => "A" # capital A, acute accent
301     # etc
302     );
303    
304     our %HTML_Escapes;
305     *HTML_Escapes = do {
306     if ($standalone) {
307     $PRETTY ? \%HTML_2_Latin_1 : \%HTML_2_ASCII_7;
308     } else {
309     \%HTML_2_Latin_1;
310     }
311     };
312    
313     *THITHER = $standalone ? *STDOUT : *STDERR;
314    
315     my %transfmt = ();
316     my $transmo = <<EOFUNC;
317     sub transmo {
318     #local \$^W = 0; # recursive warnings we do NOT need!
319     study;
320     EOFUNC
321    
322     my %msg;
323     {
324     print STDERR "FINISHING COMPILATION for $_\n" if $DEBUG;
325     local $/ = '';
326     local $_;
327     my $header;
328     my $for_item;
329     while (<POD_DIAG>) {
330    
331     unescape();
332     if ($PRETTY) {
333     sub noop { return $_[0] } # spensive for a noop
334     sub bold { my $str =$_[0]; $str =~ s/(.)/$1\b$1/g; return $str; }
335     sub italic { my $str = $_[0]; $str =~ s/(.)/_\b$1/g; return $str; }
336     s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/bold($+)/ges;
337     s/[LIF]<(.*?)>/italic($1)/ges;
338     } else {
339     s/C<<< (.*?) >>>|C<< (.*?) >>|[BC]<(.*?)>/$+/gs;
340     s/[LIF]<(.*?)>/$1/gs;
341     }
342     unless (/^=/) {
343     if (defined $header) {
344     if ( $header eq 'DESCRIPTION' &&
345     ( /Optional warnings are enabled/
346     || /Some of these messages are generic./
347     ) )
348     {
349     next;
350     }
351     s/^/ /gm;
352     $msg{$header} .= $_;
353     undef $for_item;
354     }
355     next;
356     }
357     unless ( s/=item (.*?)\s*\z//) {
358    
359     if ( s/=head1\sDESCRIPTION//) {
360     $msg{$header = 'DESCRIPTION'} = '';
361     undef $for_item;
362     }
363     elsif( s/^=for\s+diagnostics\s*\n(.*?)\s*\z// ) {
364     $for_item = $1;
365     }
366     next;
367     }
368    
369     if( $for_item ) { $header = $for_item; undef $for_item }
370     else {
371     $header = $1;
372     while( $header =~ /[;,]\z/ ) {
373     <POD_DIAG> =~ /^\s*(.*?)\s*\z/;
374     $header .= ' '.$1;
375     }
376     }
377    
378     # strip formatting directives from =item line
379     $header =~ s/[A-Z]<(.*?)>/$1/g;
380    
381     my @toks = split( /(%l?[dx]|%c|%(?:\.\d+)?s)/, $header );
382     if (@toks > 1) {
383     my $conlen = 0;
384     for my $i (0..$#toks){
385     if( $i % 2 ){
386     if( $toks[$i] eq '%c' ){
387     $toks[$i] = '.';
388     } elsif( $toks[$i] eq '%d' ){
389     $toks[$i] = '\d+';
390     } elsif( $toks[$i] eq '%s' ){
391     $toks[$i] = $i == $#toks ? '.*' : '.*?';
392     } elsif( $toks[$i] =~ '%.(\d+)s' ){
393     $toks[$i] = ".{$1}";
394     } elsif( $toks[$i] =~ '^%l*x$' ){
395     $toks[$i] = '[\da-f]+';
396     }
397     } elsif( length( $toks[$i] ) ){
398     $toks[$i] =~ s/^.*$/\Q$&\E/;
399     $conlen += length( $toks[$i] );
400     }
401     }
402     my $lhs = join( '', @toks );
403     $transfmt{$header}{pat} =
404     " s{^$lhs}\n {\Q$header\E}s\n\t&& return 1;\n";
405     $transfmt{$header}{len} = $conlen;
406     } else {
407     $transfmt{$header}{pat} =
408     " m{^\Q$header\E} && return 1;\n";
409     $transfmt{$header}{len} = length( $header );
410     }
411    
412     print STDERR "$WHOAMI: Duplicate entry: \"$header\"\n"
413     if $msg{$header};
414    
415     $msg{$header} = '';
416     }
417    
418    
419     close POD_DIAG unless *main::DATA eq *POD_DIAG;
420    
421     die "No diagnostics?" unless %msg;
422    
423     # Apply patterns in order of decreasing sum of lengths of fixed parts
424     # Seems the best way of hitting the right one.
425     for my $hdr ( sort { $transfmt{$b}{len} <=> $transfmt{$a}{len} }
426     keys %transfmt ){
427     $transmo .= $transfmt{$hdr}{pat};
428     }
429     $transmo .= " return 0;\n}\n";
430     print STDERR $transmo if $DEBUG;
431     eval $transmo;
432     die $@ if $@;
433     }
434    
435     if ($standalone) {
436     if (!@ARGV and -t STDIN) { print STDERR "$0: Reading from STDIN\n" }
437     while (defined (my $error = <>)) {
438     splainthis($error) || print THITHER $error;
439     }
440     exit;
441     }
442    
443     my $olddie;
444     my $oldwarn;
445    
446     sub import {
447     shift;
448     $^W = 1; # yup, clobbered the global variable;
449     # tough, if you want diags, you want diags.
450     return if defined $SIG{__WARN__} && ($SIG{__WARN__} eq \&warn_trap);
451    
452     for (@_) {
453    
454     /^-d(ebug)?$/ && do {
455     $DEBUG++;
456     next;
457     };
458    
459     /^-v(erbose)?$/ && do {
460     $VERBOSE++;
461     next;
462     };
463    
464     /^-p(retty)?$/ && do {
465     print STDERR "$0: I'm afraid it's too late for prettiness.\n";
466     $PRETTY++;
467     next;
468     };
469    
470     /^-t(race)?$/ && do {
471     $TRACEONLY++;
472     next;
473     };
474     /^-w(arntrace)?$/ && do {
475     $WARNTRACE++;
476     next;
477     };
478    
479     warn "Unknown flag: $_";
480     }
481    
482     $oldwarn = $SIG{__WARN__};
483     $olddie = $SIG{__DIE__};
484     $SIG{__WARN__} = \&warn_trap;
485     $SIG{__DIE__} = \&death_trap;
486     }
487    
488     sub enable { &import }
489    
490     sub disable {
491     shift;
492     return unless $SIG{__WARN__} eq \&warn_trap;
493     $SIG{__WARN__} = $oldwarn || '';
494     $SIG{__DIE__} = $olddie || '';
495     }
496    
497     sub warn_trap {
498     my $warning = $_[0];
499     if (caller eq $WHOAMI or !splainthis($warning)) {
500     if ($WARNTRACE) {
501     print STDERR Carp::longmess($warning);
502     } else {
503     print STDERR $warning;
504     }
505     }
506     goto &$oldwarn if defined $oldwarn and $oldwarn and $oldwarn ne \&warn_trap;
507     };
508    
509     sub death_trap {
510     my $exception = $_[0];
511    
512     # See if we are coming from anywhere within an eval. If so we don't
513     # want to explain the exception because it's going to get caught.
514     my $in_eval = 0;
515     my $i = 0;
516     while (my $caller = (caller($i++))[3]) {
517     if ($caller eq '(eval)') {
518     $in_eval = 1;
519     last;
520     }
521     }
522    
523     splainthis($exception) unless $in_eval;
524     if (caller eq $WHOAMI) { print STDERR "INTERNAL EXCEPTION: $exception"; }
525     &$olddie if defined $olddie and $olddie and $olddie ne \&death_trap;
526    
527     return if $in_eval;
528    
529     # We don't want to unset these if we're coming from an eval because
530     # then we've turned off diagnostics.
531    
532     # Switch off our die/warn handlers so we don't wind up in our own
533     # traps.
534     $SIG{__DIE__} = $SIG{__WARN__} = '';
535    
536     # Have carp skip over death_trap() when showing the stack trace.
537     local($Carp::CarpLevel) = 1;
538    
539     confess "Uncaught exception from user code:\n\t$exception";
540     # up we go; where we stop, nobody knows, but i think we die now
541     # but i'm deeply afraid of the &$olddie guy reraising and us getting
542     # into an indirect recursion loop
543     };
544    
545     my %exact_duplicate;
546     my %old_diag;
547     my $count;
548     my $wantspace;
549     sub splainthis {
550     return 0 if $TRACEONLY;
551     local $_ = shift;
552     local $\;
553     ### &finish_compilation unless %msg;
554     s/\.?\n+$//;
555     my $orig = $_;
556     # return unless defined;
557    
558     # get rid of the where-are-we-in-input part
559     s/, <.*?> (?:line|chunk).*$//;
560    
561     # Discard 1st " at <file> line <no>" and all text beyond
562     # but be aware of messsages containing " at this-or-that"
563     my $real = 0;
564     my @secs = split( / at / );
565     $_ = $secs[0];
566     for my $i ( 1..$#secs ){
567     if( $secs[$i] =~ /.+? (?:line|chunk) \d+/ ){
568     $real = 1;
569     last;
570     } else {
571     $_ .= ' at ' . $secs[$i];
572     }
573     }
574    
575     # remove parenthesis occurring at the end of some messages
576     s/^\((.*)\)$/$1/;
577    
578     if ($exact_duplicate{$orig}++) {
579     return &transmo;
580     } else {
581     return 0 unless &transmo;
582     }
583    
584     $orig = shorten($orig);
585     if ($old_diag{$_}) {
586     autodescribe();
587     print THITHER "$orig (#$old_diag{$_})\n";
588     $wantspace = 1;
589     } else {
590     autodescribe();
591     $old_diag{$_} = ++$count;
592     print THITHER "\n" if $wantspace;
593     $wantspace = 0;
594     print THITHER "$orig (#$old_diag{$_})\n";
595     if ($msg{$_}) {
596     print THITHER $msg{$_};
597     } else {
598     if (0 and $standalone) {
599     print THITHER " **** Error #$old_diag{$_} ",
600     ($real ? "is" : "appears to be"),
601     " an unknown diagnostic message.\n\n";
602     }
603     return 0;
604     }
605     }
606     return 1;
607     }
608    
609     sub autodescribe {
610     if ($VERBOSE and not $count) {
611     print THITHER &{$PRETTY ? \&bold : \&noop}("DESCRIPTION OF DIAGNOSTICS"),
612     "\n$msg{DESCRIPTION}\n";
613     }
614     }
615    
616     sub unescape {
617     s {
618     E<
619     ( [A-Za-z]+ )
620     >
621     } {
622     do {
623     exists $HTML_Escapes{$1}
624     ? do { $HTML_Escapes{$1} }
625     : do {
626     warn "Unknown escape: E<$1> in $_";
627     "E<$1>";
628     }
629     }
630     }egx;
631     }
632    
633     sub shorten {
634     my $line = $_[0];
635     if (length($line) > 79 and index($line, "\n") == -1) {
636     my $space_place = rindex($line, ' ', 79);
637     if ($space_place != -1) {
638     substr($line, $space_place, 1) = "\n\t";
639     }
640     }
641     return $line;
642     }
643    
644    
645     1 unless $standalone; # or it'll complain about itself
646     __END__ # wish diag dbase were more accessible