Skip Menu |

This queue is for tickets about the Perl-Tidy CPAN distribution.

Report information
The Basics
Id: 78182
Status: resolved
Priority: 0/
Queue: Perl-Tidy

People
Owner: Nobody in particular
Requestors: thaljef [...] cpan.org
Cc: dev [...] perlcritic.tigris.org
AdminCc:

Bug Information
Severity: (no value)
Broken in: 20120701
Fixed in: (no value)



CC: dev [...] perlcritic.tigris.org
Subject: Strange side effects on STDERR
I noticed that the latest version of Perl::Tidy requires the stderr parameter to be an actual filehandle, not just a scalar reference. So if I want to capture stderr into a scalar, I must use an IO::String object or something similar. The problem is that Perl::Tidy assigns that object directly to *STDERR. And when that object goes out of scope, it closes STDERR. So subsequent calls to warn() fail because the output handle is now gone. The attached file demonstrates this. I think the solution might be for Perl::Tidy to use a lexical (or localized) filehandle rather than STDERR directly. Thanks for your consideration. -- Jeffrey Thalhammer Imaginative Software Systems www.imaginative-software.com
Subject: test.pl
#!perl use strict; use warnings; use IO::String; use Perl::Tidy 20120701; my $tidy_src = ''; my $raw_src = q{ print ('Hello World') ; }; { my $stderr_buffer = ''; my $stderr_fh = IO::String->new( \$stderr_buffer ); local @ARGV = qw(-nst -nse); Perl::Tidy::perltidy( destination => \$tidy_src, source => \$raw_src, stderr => $stderr_fh ); } # This next line throws a fatal exception because Perl::Tidy has tied # STDERR to our $stderr_fh (which is an IO::String object), and when # that object is destroyed, it closes the handle (thus closing # STDERR). So calling warn() will fail because the output handle # isn't there any more. Strangely, calling CORE::warn still works. warn "Tidied code is $tidy_src";
Jeffrey, Thanks for the example. Getting STDERR output to a string is tricky because it's very easy to lose it under certain error conditions. I played with the example and found that localizing *STDERR solves part of the problem but I had to make some additional changes to the test case avoid losing the error output. I had to move the string buffer outside of the block because for certain errors it will be lost before it can be printed. Also, I had to eval the block to avoid losing everything in some cases. See the attached example. In the example I localized *STDERR in the block, but I also got the same result by localizing it inside of the Tidy.pm module. So I may localize it in the next release if I don't discover any problems, but meanwhile you could localize it in the calling routine as a workaround. But note that what you actually may want to capture is the 'errorfile', which is what would go to a .ERR file in case problems are detected in the source code, and there is no problem sending that to a string reference. Also, sending STDERR to a real file seems to work reliably. If you make any progress on this issue I would be interested. Steve
Subject: stderr.pl
#!perl use strict; use warnings; use IO::String; use Perl::Tidy 20120701; # Trying to capture perltidy STDERR output into a string my $tidy_src = ''; my $raw_src = q{ print ['Hello World') ; }; # NOTE ERROR my $stderr_buffer = ''; # Note: must define outside of eval block my $stderr_fh = IO::String->new( \$stderr_buffer ); my $errorfile; eval { # we lose standard error output without eval # This is a good parameter set: local @ARGV = qw(-nst -nse ); # But uncomment this to generate an error condition: # push @ARGV,"-xyzzy"; local *STDERR = *STDERR; # this localization could go inside of Tidy.pm Perl::Tidy::perltidy( destination => \$tidy_src, source => \$raw_src, stderr => $stderr_fh, errorfile => \$errorfile, ); # we will never get here for some error conditions print STDOUT "May never get here if serious error\n"; }; # send something to stderr warn "Tidied code is $tidy_src"; print "\n"; print "..............Begin Error Buffer...........\n"; print "$stderr_buffer"; print "..............End Error Buffer...........\n"; print "\n"; if ($errorfile) { print "..............Begin .ERR file ...........\n"; print "$errorfile"; print "..............End .ERR file...........\n"; } else { print "..............Empty .ERR file ...........\n"; } print "\n"; print "..............Begin Tidied Code ...........\n"; print "$tidy_src"; print "..............End Tidied Code...........\n";
Subject: Re: [rt.cpan.org #78182] Strange side effects on STDERR
Date: Tue, 3 Jul 2012 22:44:33 -0700
To: bug-Perl-Tidy [...] rt.cpan.org
From: Jeffrey Thalhammer <jeff [...] imaginative-software.com>
On Jul 3, 2012, at 5:38 PM, Steve Hancock via RT wrote: Show quoted text
> <URL: https://rt.cpan.org/Ticket/Display.html?id=78182 > > > Thanks for the example. Getting STDERR output to a string is tricky > because it's very easy to lose it under certain error conditions.
What conditions do you have in mind? Are you concerned about trapping warnings from your code or from someone else's code? The former should be pretty straightforward to trap. For the latter, I've had good luck with exploiting $SIG{__WARN__}. Show quoted text
> I played with the example and found that localizing *STDERR solves part of > the problem but I had to make some additional changes to the test case > avoid losing the error output. I had to move the string buffer outside > of the block because for certain errors it will be lost before it can be > printed. Also, I had to eval the block to avoid losing everything in > some cases.
It's not lost -- the program just stops because Perl::Tidy throws an exception for those "certain errors" (like bogus options in @ARGV). But since STDERR has been globally reassigned to another handle, the error message never becomes visible. Wrapping it in an eval{} traps the exception and gives you a chance to examine the messages on that handle afterwards. But that shouldn't be mandatory. Without the eval{}, exceptions should come out on whatever *my* STDERR is, not Perl::Tidy's notion of STDERR. Show quoted text
> In the example I localized *STDERR in the block, but I also got the same > result by localizing it inside of the Tidy.pm module. So I may localize > it in the next release if I don't discover any problems, but meanwhile > you could localize it in the calling routine as a workaround.
I would probably use a scalar that points to either \*STDERR or the handle specified by the 'stderr' option (if given). Then print all the warnings to that scalar, instead of STDERR. See the attached patch for an example. That eliminates the need for an eval{} and fixes the problem with inadvertently closing the real STDERR. Show quoted text
> But note that what you actually may want to capture is the 'errorfile', > which is what would go to a .ERR file in case problems are detected in > the source code, and there is no problem sending that to a string > reference.
I didn't know about the errorfile -- thanks for pointing that out! So what I really want now is to just trap stderr *and* all the log messages into one scalar. I can do that by giving 'stderr' and 'errorfile' the same IO::String. So that problem is solved :) In my use case, the distinction between stderr and the errorfile is a bit artificial. Perl::Critic feeds one file at a time to Perl::Tidy. So from that perspective, there only needs to be two output channels: 1) the tidied code; 2) everything else (errors, warnings, diagnostics, etc.) But I realize that there are other use cases too. Show quoted text
> Also, sending STDERR to a real file seems to work reliably.
That would be my last resort. But it is good to know I can fall back to that if necessary. Thanks for making such a useful tool! -Jeff

Message body is not shown because sender requested not to inline it.

Jeff, Thanks for your suggestions. Either localizing STDERR or your patch will solve the immediate problem of losing the standard error output, but the key issue that I see is: can an eval be avoided if standard error output goes to a string. Right now the best solution that I see is to modify Perl::Tidy to eliminate the 'stderr' call parameter and at the same time make all non- catastrophic errors (1) write messages to the 'errorfile', if it is available, and STDERR if not (2) set an error return flag, and (3) do a normal return. With this change, the only output to STDERR should be programming errors that require immediate attention, which might occur during program development. I am going to look into this further. Steve
CC: "thaljef [...] cpan.org" <thaljef [...] cpan.org>, "dev [...] perlcritic.tigris.org" <dev [...] perlcritic.tigris.org>
Subject: Re: [rt.cpan.org #78182] Strange side effects on STDERR
Date: Wed, 11 Jul 2012 10:23:46 -0700
To: "bug-Perl-Tidy [...] rt.cpan.org" <bug-Perl-Tidy [...] rt.cpan.org>
From: Jeffrey Thalhammer <jeff [...] imaginative-software.com>
Just a little more clarity on the root cause... STDERR becomes undefined after running perltidy() because it is being assigned to a lexical variable. So when it goes out of scope, so does STDERR. It has nothing to do with the use of IO::String. I think the right thing todo is to just localize STDERR inside perltidy (). That also eliminates the need to wrap it with an eval{}.
This is fixed in version 20120714. The attached example illustrates usage. From the Changelog: - Fixed RT #78182, side effects with STDERR. Error handling has been revised and the documentation has been updated. STDERR can now be redirected to a string reference, and perltidy now returns an error flag instead of calling die when input errors are detected. If the error flag is set then no tidied output was produced. See man Perl::Tidy for an example.
Subject: ex_mp.pl
#!/usr/bin/perl -w # example call to perltidy from man page documentation of Perl::Tidy use strict; use Perl::Tidy; my $source_string = <<'EOT'; my$error=Perl::Tidy::perltidy(argv=>$argv,source=>\$source_string, destination=>\$dest_string,stderr=>\$stderr_string, errorfile=>\$errorfile_string,); EOT my $dest_string; my $stderr_string; my $errorfile_string; my $argv = "-npro"; # Ignore any .perltidyrc at this site $argv .= " -pbp"; # Format according to perl best practices $argv .= " -nst"; # Must turn off -st in case -pbp is specified $argv .= " -se"; # -se appends the errorfile to stderr ## $argv .= " --spell-check"; # uncomment to trigger an error print "<<RAW SOURCE>>\n$source_string\n"; my $error = Perl::Tidy::perltidy( argv => $argv, source => \$source_string, destination => \$dest_string, stderr => \$stderr_string, errorfile => \$errorfile_string, # not used when -se flag is set ##phasers => 'stun', # uncomment to trigger an error ); if ($error) { # serious error in input parameters, no tidied output print "<<STDERR>>\n$stderr_string\n"; die "Exiting because of serious errors\n"; } if ($dest_string) { print "<<TIDIED SOURCE>>\n$dest_string\n" } if ($stderr_string) { print "<<STDERR>>\n$stderr_string\n" } if ($errorfile_string) { print "<<.ERR file>>\n$errorfile_string\n" }