Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PATCH] Locale::Maketext - $@ being localized suppresses error on die event. #10676

Closed
p5pRT opened this issue Sep 28, 2010 · 12 comments
Closed

Comments

@p5pRT
Copy link

p5pRT commented Sep 28, 2010

Migrated from rt.perl.org#78108 (status was 'resolved')

Searchable as RT78108$

@p5pRT
Copy link
Author

p5pRT commented Sep 28, 2010

From @toddr

Created by @toddr

This patch addresses CPAN RT 34182 (Locale​::Maketext). $@​ is localized only in scope so die messages fall through when desired.

Previously, there was test code to make sure $@​ was not modified when maketext is called, but if the caller wraps maketext in an eval, then it's going to be modified anyway to '' at the least. If the caller does not wrap a maketext call in an eval and maketext dies, then hiding the $@​ simply confuses the person debugging as to what went wrong.

Perl Info

Flags:
   category=library
   severity=low
   module=Locale::Maketext
   Type=Patch
   PatchStatus=HasPatch

Site configuration information for perl 5.12.1:

Configured by cPanel at Mon Jun  7 11:16:28 CDT 2010.

Summary of my perl5 (revision 5 version 12 subversion 1) configuration:

 Platform:
   osname=linux, osvers=2.6.18-194.3.1.el5, archname=x86_64-linux
   uname='linux rpmb-centos-50-64bit 2.6.18-194.3.1.el5 #1 smp thu may 13 13:08:30 edt 2010 x86_64 x86_64 x86_64 gnulinux '
   config_args='-des -Darchname=x86_64-linux -Dcc=/usr/local/cpanel/bin/gcc -Dcpp=/usr/local/cpanel/bin/gcc -E -DDEBUGGING=none -Doptimize=-Os -Dusemymalloc=y -Duseshrplib=true -Duselargefiles=yes -Duseposix=true -Dhint=recommended -Duseperlio=yes -Dccflags=-I/usr/local/cpanel/include -L/usr/local/cpanel/lib64 -Wl,-rpath -Wl,/usr/local/cpanel/lib64  -Dcppflags=-I/usr/local/cpanel/include -L/usr/local/cpanel/lib64 -Dldflags=-Wl,-rpath -Wl,/usr/local/cpanel/lib64 -L/usr/local/cpanel/lib64 -Dprefix=/usr/local/cpanel -Dsiteprefix=/usr/local/cpanel -Dsitebin=/usr/local/cpanel/bin -Dsitelib=/usr/local/cpanel/lib64/perl5/site_lib -Dprivlib=/usr/local/cpanel/lib64/perl5/5.12.1 -Dotherlibdirs=/var/cpanel/perl5/lib:/usr/local/cpanel/lib64/perl5/cpanel -Dman1dir=/usr/local/cpanel/share/man/man1 -Dman3dir=/usr/local/cpanel/share/man/man3 -Dsiteman1dir=/usr/local/cpanel/share/man/man1 -Dsiteman3dir=/usr/local/cpanel/share/man/man3 -Dcf_by=cPanel -Dmyhostname=localhost -Dperladmin=root@localhost -Dcf_email=support@cpanel.net -Di_dbm=/usr/local/cpanel/include -Di_gdbm=/usr/local/cpanel/include -Di_ndbm=/usr/local/cpanel/include -Ud_dosuid -Uuserelocatableinc -Umad -Uusethreads -Uusemultiplicity -Uusesocks -Uuselongdouble -Ui_db -Aldflags=-L/usr/local/cpanel/lib64 -L/usr/lib64 -L/lib64 -lgdbm -Dlocincpth=/usr/local/cpanel/include /usr/local/include  -Acflags=-fPIC -DPIC -m64 -I/usr/local/cpanel/include -Dlibpth=/usr/local/cpanel/lib64 /usr/local/lib64 /usr/local/lib /lib64 /usr/lib64  -Duse64bitint=yes -Duse64bitall=yes'
   hint=recommended, useposix=true, d_sigaction=define
   useithreads=undef, usemultiplicity=undef
   useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
   use64bitint=define, use64bitall=define, uselongdouble=undef
   usemymalloc=y, bincompat5005=undef
 Compiler:
   cc='/usr/local/cpanel/bin/gcc', ccflags ='-I/usr/local/cpanel/include -L/usr/local/cpanel/lib64 -Wl,-rpath -Wl,/usr/local/cpanel/lib64 -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/cpanel/include -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64',
   optimize='-Os',
   cppflags='-I/usr/local/cpanel/include -L/usr/local/cpanel/lib64 -I/usr/local/cpanel/include -L/usr/local/cpanel/lib64 -Wl,-rpath -Wl,/usr/local/cpanel/lib64 -fno-strict-aliasing -pipe -fstack-protector -I/usr/local/cpanel/include -I/usr/local/include'
   ccversion='', gccversion='4.5.0', gccosandvers=''
   intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678
   d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
   ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
   alignbytes=8, prototype=define
 Linker and Libraries:
   ld='/usr/local/cpanel/bin/gcc', ldflags ='-Wl,-rpath -Wl,/usr/local/cpanel/lib64 -L/usr/local/cpanel/lib64 -L/usr/local/cpanel/lib64 -L/usr/lib64 -L/lib64 -lgdbm -fstack-protector -L/usr/local/lib'
   libpth=/usr/local/cpanel/lib64 /usr/local/lib64 /usr/local/lib /lib64 /usr/lib64
   libs=-lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc -lgdbm_compat
   perllibs=-lnsl -ldl -lm -lcrypt -lutil -lc
   libc=/lib/libc-2.5.so, so=so, useshrplib=true, libperl=libperl.so
   gnulibc_version='2.5'
 Dynamic Linking:
   dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E -Wl,-rpath,/usr/local/cpanel/lib64/perl5/5.12.1/x86_64-linux/CORE'
   cccdlflags='-fPIC', lddlflags='-shared -Os -L/usr/local/cpanel/lib64 -L/usr/lib64 -L/lib64 -L/usr/local/lib -fstack-protector'

Locally applied patches:
   cPanel Patches


@INC for perl 5.12.1:
   /usr/local/cpanel/lib64/perl5/site_lib/x86_64-linux
   /usr/local/cpanel/lib64/perl5/site_lib
   /usr/local/cpanel/lib64/perl5/5.12.1/x86_64-linux
   /usr/local/cpanel/lib64/perl5/5.12.1
   /var/cpanel/perl5/lib
   /usr/local/cpanel/lib64/perl5/cpanel
   .


Environment for perl 5.12.1:
   HOME=/home/toddr
   LANG=en_US.UTF-8
   LANGUAGE (unset)
   LD_LIBRARY_PATH (unset)
   LOGDIR (unset)
   PATH=/usr/local/cpanel/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:
   PERL_BADLANG (unset)
   SHELL=/bin/zsh


@p5pRT
Copy link
Author

p5pRT commented Sep 28, 2010

From @toddr

commit 46d33563971797366d5a085fcf898e4523a11d83
Author​: Todd Rinaldo <toddr@​cpan.org>
Date​: Tue Sep 28 14​:06​:43 2010 -0500

  CPAN RT 34182 (Locale​::Maketext) - Don't unnecessarily localize $@​.
  Do it in scope only so die messages fall through when desired.
 
  Previously, there was test code to make sure $@​ was not modified when
  maketext is called, but if the caller wraps maketext in an eval, then
  it's going to be modified anyways to '' at the least. If the caller
  does not wrap a maketext call in an eval and maketext dies, then hiding
  the $@​ simply confuses the person debugging as to what went wrong.

Inline Patch
diff --git a/dist/Locale-Maketext/lib/Locale/Maketext.pm b/dist/Locale-Maketext/lib/Locale/Maketext.pm
index 5479a60..f760d4f 100644
--- a/dist/Locale-Maketext/lib/Locale/Maketext.pm
+++ b/dist/Locale-Maketext/lib/Locale/Maketext.pm
@@ -160,12 +160,11 @@ sub failure_handler_auto {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
@@ -195,10 +194,6 @@ sub maketext {
     my($handle, $phrase) = splice(@_,0,2);
     Carp::confess('No handle/phrase') unless (defined($handle) && defined($phrase));
 
-
-    # Don't interefere with $@ in case that's being interpolated into the msg.
-    local $@;
-
     # Look up the value:
 
     my $value;
@@ -272,12 +267,11 @@ sub maketext {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if ($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
@@ -434,10 +428,11 @@ sub _try_use {   # Basically a wrapper around "require Modulename"
     }
 
     DEBUG and warn " About to use $module ...\n";
-    {
-        local $SIG{'__DIE__'};
-        eval "require $module"; # used to be "use $module", but no point in that.
-    }
+
+    local $SIG{'__DIE__'};
+    local $@;
+    eval "require $module"; # used to be "use $module", but no point in that.
+
     if($@) {
         DEBUG and warn "Error using $module \: $@\n";
         return $tried{$module} = 0;
diff --git a/dist/Locale-Maketext/t/30_eval_dollar_at.t b/dist/Locale-Maketext/t/30_eval_dollar_at.t
new file mode 100644
index 0000000..17ae48a
--- /dev/null
+++ b/dist/Locale-Maketext/t/30_eval_dollar_at.t
@@ -0,0 +1,49 @@
+use strict;
+use warnings;
+
+{
+    package TEST;
+    use base 'Locale::Maketext';
+}
+
+{
+    package TEST::en;
+    use base 'TEST';
+    our %Lexicon = (
+        _AUTO => 1,
+    );
+}
+
+package main;
+use strict;
+use warnings;
+use Test::More tests => 9;
+
+my $lh = TEST->get_handle('en');
+is($lh->maketext("This works fine"), "This works fine", "straight forward _AUTO string test");
+
+my $err = eval {
+   $lh->maketext('this is ] an error');
+};
+is($err, undef, "no return from eval");
+like("$@", qr/Unbalanced\s'\]',\sin/ms, '$@ shows that ] was unbalanced');  
+
+# _try_use doesn't pollute $@
+$@ = '';
+is(Locale::Maketext::_try_use("This::module::does::not::exist"), 0, "0 return if module is missing when _try_use is called");
+is($@, '', '$@ is clean after failed _try_use');
+
+# _try_use doesn't pollute $@ for valid call
+$@ = '';
+is(Locale::Maketext::_try_use("Locale::Maketext::Guts"), 1, "1 return using valid module Locale::Maketext::Guts");
+is($@, '', '$@ is clean after failed _try_use');
+
+# failure_handler_auto handles $@ locally.
+{
+    $@ = '';
+    my $err = '';
+    $lh->{failure_lex}->{"foo_fail"} = sub {die("fail message");};
+    $err = eval {$lh->failure_handler_auto("foo_fail")};
+    is($err, undef, "die event calling failure_handler on bad code");
+    like($@, qr/^Error in maketexting "foo_fail":/ms, "\$@ is re-written as expected.");
+}
diff --git a/dist/Locale-Maketext/t/30_local.t b/dist/Locale-Maketext/t/30_local.t
deleted file mode 100644
index 23fa2ac..0000000
--- a/dist/Locale-Maketext/t/30_local.t
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/perl -Tw
-
-use strict;
-
-use Test::More tests => 3;
-use Locale::Maketext;
-
-# declare a class...
-{
-  package Woozle;
-  our @ISA = ('Locale::Maketext');
-  our %Lexicon = (
-    _AUTO => 1
-  );
-  keys %Lexicon; # dodges the 'used only once' warning
-}
-
-my $lh = Woozle->new();
-isa_ok($lh, 'Woozle');
-
-$@ = 'foo';
-is($lh->maketext('Eval error: [_1]', $@), 'Eval error: foo', "Make sure \$@ is localized when passed to maketext");
-is($@, 'foo', "\$@ wasn't modified during call");

@p5pRT
Copy link
Author

p5pRT commented Oct 3, 2010

From @cpansprout

On Tue Sep 28 13​:41​:01 2010, toddr@​cpanel.net wrote​:

This is a bug report for perl from toddr@​cpanel.net,
generated with the help of perlbug 1.39 running under perl 5.12.1.

-----------------------------------------------------------------
[Please describe your issue here]

This patch addresses CPAN RT 34182 (Locale​::Maketext). $@​ is localized
only in scope so die messages fall through when desired.

Previously, there was test code to make sure $@​ was not modified when
maketext is called, but if the caller wraps maketext in an eval,
then it's going to be modified anyway to '' at the least. If the
caller does not wrap a maketext call in an eval and maketext dies,
then hiding the $@​ simply confuses the person debugging as to what
went wrong.

Concerning these two lines​:
- # Don't interefere with $@​ in case that's being interpolated into
the msg.
- local $@​;

Code that currently relies on $@​ keeping its existing value will start
to fail. I would suggest a ‘my $at = $@​;’ and then ‘$@​ = $at’ before the
return at the end of the sub.

(BTW, the local $@​ is fine for blead, in which die assigns to $@​ after
unwinding the stack, but causes problems in earlier perl versions.)

@p5pRT
Copy link
Author

p5pRT commented Oct 3, 2010

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

On Oct 3, 2010, at 4​:20 PM, Father Chrysostomos via RT wrote​:

On Tue Sep 28 13​:41​:01 2010, toddr@​cpanel.net wrote​:

This is a bug report for perl from toddr@​cpanel.net,
generated with the help of perlbug 1.39 running under perl 5.12.1.

-----------------------------------------------------------------
[Please describe your issue here]

This patch addresses CPAN RT 34182 (Locale​::Maketext). $@​ is localized
only in scope so die messages fall through when desired.

Previously, there was test code to make sure $@​ was not modified when
maketext is called, but if the caller wraps maketext in an eval,
then it's going to be modified anyway to '' at the least. If the
caller does not wrap a maketext call in an eval and maketext dies,
then hiding the $@​ simply confuses the person debugging as to what
went wrong.

Concerning these two lines​:
- # Don't interefere with $@​ in case that's being interpolated into
the msg.
- local $@​;

Code that currently relies on $@​ keeping its existing value will start
to fail. I would suggest a ‘my $at = $@​;’ and then ‘$@​ = $at’ before the
return at the end of the sub.

(BTW, the local $@​ is fine for blead, in which die assigns to $@​ after
unwinding the stack, but causes problems in earlier perl versions.)

I agree with you about current code depending on $@​ being localized.

Attached is the diff for the additional code.

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

Inline Patch
diff --git a/dist/Locale-Maketext/lib/Locale/Maketext.pm b/dist/Locale-Maketext/lib/Locale/Maketext.pm
index f760d4f..5b367ff 100644
--- a/dist/Locale-Maketext/lib/Locale/Maketext.pm
+++ b/dist/Locale-Maketext/lib/Locale/Maketext.pm
@@ -27,7 +27,7 @@ BEGIN {
 }
 
 
-$VERSION = '1.16';
+$VERSION = '1.15_01';
 @ISA = ();
 
 $MATCH_SUPERS = 1;
@@ -194,6 +194,10 @@ sub maketext {
     my($handle, $phrase) = splice(@_,0,2);
     Carp::confess('No handle/phrase') unless (defined($handle) && defined($phrase));
 
+    # backup $@ in case it it's still being used in the calling code.
+    # If no failures, we'll re-set it back to what it was later.
+    my $at = $@;
+
     # Look up the value:
 
     my $value;
@@ -243,10 +247,12 @@ sub maketext {
             DEBUG and warn "WARNING0: maketext fails looking for <$phrase>\n";
             my $fail;
             if(ref($fail = $handle->{'fail'}) eq 'CODE') { # it's a sub reference
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return &{$fail}($handle, $phrase, @_);
                 # If it ever returns, it should return a good value.
             }
             else { # It's a method name
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return $handle->$fail($phrase, @_);
                 # If it ever returns, it should return a good value.
             }
@@ -257,8 +263,14 @@ sub maketext {
         }
     }
 
-    return $$value if ref($value) eq 'SCALAR';
-    return $value unless ref($value) eq 'CODE';
+    if(ref($value) eq 'SCALAR'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $$value ;
+    }
+    if(ref($value) ne 'CODE'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $value ;
+    }
 
     {
         local $SIG{'__DIE__'};
@@ -276,8 +288,10 @@ sub maketext {
         # a method that didn't exist.
     }
     else {
+        $@ = $at; # Put $@ back in case we altered it along the way.
         return $value;
     }
+    $@ = $at; # Put $@ back in case we altered it along the way.
 }
 
 ###########################################################################
diff --git a/dist/Locale-Maketext/t/30_eval_dollar_at.t b/dist/Locale-Maketext/t/30_eval_dollar_at.t
index 17ae48a..523365d 100644
--- a/dist/Locale-Maketext/t/30_eval_dollar_at.t
+++ b/dist/Locale-Maketext/t/30_eval_dollar_at.t
@@ -17,10 +17,12 @@ use warnings;
 package main;
 use strict;
 use warnings;
-use Test::More tests => 9;
+use Test::More tests => 10;
 
 my $lh = TEST->get_handle('en');
+$@ = "foo";
 is($lh->maketext("This works fine"), "This works fine", "straight forward _AUTO string test");
+is($@, "foo", q{$@ isn't altered during calls to maketext});
 
 my $err = eval {
    $lh->maketext('this is ] an error');
@@ -29,9 +31,9 @@ is($err, undef, "no return from eval");
 like("$@", qr/Unbalanced\s'\]',\sin/ms, '$@ shows that ] was unbalanced');  
 
 # _try_use doesn't pollute $@
-$@ = '';
+$@ = 'foo2';
 is(Locale::Maketext::_try_use("This::module::does::not::exist"), 0, "0 return if module is missing when _try_use is called");
-is($@, '', '$@ is clean after failed _try_use');
+is($@, 'foo2', '$@ is unmodified by a failed _try_use');
 
 # _try_use doesn't pollute $@ for valid call
 $@ = '';

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

The revised full patch (patch2.txt) is also attached​:

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

commit 17a38f9c9a77c2e7c8d5bff45e8363c50c5578c5
Author​: Todd Rinaldo <toddr@​cpan.org>
Date​: Mon Oct 4 15​:44​:17 2010 -0500

  CPAN RT 34182 (Locale​::Maketext) - Don't unnecessarily localize $@​.
  Do it in scope only so die messages fall through when desired.
 
  Previously, there was test code to make sure $@​ was not modified when
  maketext is called, but if the caller wraps maketext in an eval, then
  it's going to be modified anyways to '' at the least. If the caller
  does not wrap a maketext call in an eval and maketext dies, then hiding
  the $@​ simply confuses the person debugging as to what went wrong.
 
  We do however backup/restore $@​ so that it does not break any code that
  looks might use $@​ after a successful call to maketext.
  eval {...}
  $lm-&gt;maketext($@​);
  do_something_else($@​);
  In the above example, $@​ would be the same when passed to do_something_else

Inline Patch
diff --git a/dist/Locale-Maketext/ChangeLog b/dist/Locale-Maketext/ChangeLog
index 16891a1..7f9b070 100644
--- a/dist/Locale-Maketext/ChangeLog
+++ b/dist/Locale-Maketext/ChangeLog
@@ -6,6 +6,9 @@ Revision history for Perl suite Locale::Maketext
     Fix for CPAN RT #40727: infinite loop in
     Locale::Maketext::Guts::_compile() when working with tainted values
 
+    Fix for CPAN RT #34182: Don't localize $@. 
+    ->maketext calls will now backup and restore $@ so that die messages are not supressed.
+	
 2010−06−22
     * Release 1.15 (included in perl 5.13.3; not released separately)
 
diff --git a/dist/Locale-Maketext/lib/Locale/Maketext.pm b/dist/Locale-Maketext/lib/Locale/Maketext.pm
index 5479a60..5b367ff 100644
--- a/dist/Locale-Maketext/lib/Locale/Maketext.pm
+++ b/dist/Locale-Maketext/lib/Locale/Maketext.pm
@@ -27,7 +27,7 @@ BEGIN {
 }
 
 
-$VERSION = '1.16';
+$VERSION = '1.15_01';
 @ISA = ();
 
 $MATCH_SUPERS = 1;
@@ -160,12 +160,11 @@ sub failure_handler_auto {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
@@ -195,9 +194,9 @@ sub maketext {
     my($handle, $phrase) = splice(@_,0,2);
     Carp::confess('No handle/phrase') unless (defined($handle) && defined($phrase));
 
-
-    # Don't interefere with $@ in case that's being interpolated into the msg.
-    local $@;
+    # backup $@ in case it it's still being used in the calling code.
+    # If no failures, we'll re-set it back to what it was later.
+    my $at = $@;
 
     # Look up the value:
 
@@ -248,10 +247,12 @@ sub maketext {
             DEBUG and warn "WARNING0: maketext fails looking for <$phrase>\n";
             my $fail;
             if(ref($fail = $handle->{'fail'}) eq 'CODE') { # it's a sub reference
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return &{$fail}($handle, $phrase, @_);
                 # If it ever returns, it should return a good value.
             }
             else { # It's a method name
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return $handle->$fail($phrase, @_);
                 # If it ever returns, it should return a good value.
             }
@@ -262,8 +263,14 @@ sub maketext {
         }
     }
 
-    return $$value if ref($value) eq 'SCALAR';
-    return $value unless ref($value) eq 'CODE';
+    if(ref($value) eq 'SCALAR'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $$value ;
+    }
+    if(ref($value) ne 'CODE'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $value ;
+    }
 
     {
         local $SIG{'__DIE__'};
@@ -272,18 +279,19 @@ sub maketext {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if ($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
     else {
+        $@ = $at; # Put $@ back in case we altered it along the way.
         return $value;
     }
+    $@ = $at; # Put $@ back in case we altered it along the way.
 }
 
 ###########################################################################
@@ -434,10 +442,11 @@ sub _try_use {   # Basically a wrapper around "require Modulename"
     }
 
     DEBUG and warn " About to use $module ...\n";
-    {
-        local $SIG{'__DIE__'};
-        eval "require $module"; # used to be "use $module", but no point in that.
-    }
+
+    local $SIG{'__DIE__'};
+    local $@;
+    eval "require $module"; # used to be "use $module", but no point in that.
+
     if($@) {
         DEBUG and warn "Error using $module \: $@\n";
         return $tried{$module} = 0;
diff --git a/dist/Locale-Maketext/t/30_eval_dollar_at.t b/dist/Locale-Maketext/t/30_eval_dollar_at.t
new file mode 100644
index 0000000..523365d
--- /dev/null
+++ b/dist/Locale-Maketext/t/30_eval_dollar_at.t
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+
+{
+    package TEST;
+    use base 'Locale::Maketext';
+}
+
+{
+    package TEST::en;
+    use base 'TEST';
+    our %Lexicon = (
+        _AUTO => 1,
+    );
+}
+
+package main;
+use strict;
+use warnings;
+use Test::More tests => 10;
+
+my $lh = TEST->get_handle('en');
+$@ = "foo";
+is($lh->maketext("This works fine"), "This works fine", "straight forward _AUTO string test");
+is($@, "foo", q{$@ isn't altered during calls to maketext});
+
+my $err = eval {
+   $lh->maketext('this is ] an error');
+};
+is($err, undef, "no return from eval");
+like("$@", qr/Unbalanced\s'\]',\sin/ms, '$@ shows that ] was unbalanced');  
+
+# _try_use doesn't pollute $@
+$@ = 'foo2';
+is(Locale::Maketext::_try_use("This::module::does::not::exist"), 0, "0 return if module is missing when _try_use is called");
+is($@, 'foo2', '$@ is unmodified by a failed _try_use');
+
+# _try_use doesn't pollute $@ for valid call
+$@ = '';
+is(Locale::Maketext::_try_use("Locale::Maketext::Guts"), 1, "1 return using valid module Locale::Maketext::Guts");
+is($@, '', '$@ is clean after failed _try_use');
+
+# failure_handler_auto handles $@ locally.
+{
+    $@ = '';
+    my $err = '';
+    $lh->{failure_lex}->{"foo_fail"} = sub {die("fail message");};
+    $err = eval {$lh->failure_handler_auto("foo_fail")};
+    is($err, undef, "die event calling failure_handler on bad code");
+    like($@, qr/^Error in maketexting "foo_fail":/ms, "\$@ is re-written as expected.");
+}
diff --git a/dist/Locale-Maketext/t/30_local.t b/dist/Locale-Maketext/t/30_local.t
deleted file mode 100644
index 23fa2ac..0000000
--- a/dist/Locale-Maketext/t/30_local.t
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/perl -Tw
-
-use strict;
-
-use Test::More tests => 3;
-use Locale::Maketext;
-
-# declare a class...
-{
-  package Woozle;
-  our @ISA = ('Locale::Maketext');
-  our %Lexicon = (
-    _AUTO => 1
-  );
-  keys %Lexicon; # dodges the 'used only once' warning
-}
-
-my $lh = Woozle->new();
-isa_ok($lh, 'Woozle');
-
-$@ = 'foo';
-is($lh->maketext('Eval error: [_1]', $@), 'Eval error: foo', "Make sure \$@ is localized when passed to maketext");
-is($@, 'foo', "\$@ wasn't modified during call");

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

I agree with you about current code depending on $@​ being localized.

Attached is the diff for the additional code.
<dollar_at.txt>

The revised full patch (patch2.txt) is also attached​:
<patch2.txt>

#p5p just pointed out I accidentally downgraded the version. Submitting patch3.txt

@p5pRT
Copy link
Author

p5pRT commented Oct 4, 2010

From @toddr

commit 17a38f9c9a77c2e7c8d5bff45e8363c50c5578c5
Author​: Todd Rinaldo <toddr@​cpan.org>
Date​: Mon Oct 4 15​:44​:17 2010 -0500

  CPAN RT 34182 (Locale​::Maketext) - Don't unnecessarily localize $@​.
  Do it in scope only so die messages fall through when desired.
 
  Previously, there was test code to make sure $@​ was not modified when
  maketext is called, but if the caller wraps maketext in an eval, then
  it's going to be modified anyways to '' at the least. If the caller
  does not wrap a maketext call in an eval and maketext dies, then hiding
  the $@​ simply confuses the person debugging as to what went wrong.
 
  We do however backup/restore $@​ so that it does not break any code that
  looks might use $@​ after a successful call to maketext.
  eval {...}
  $lm-&gt;maketext($@​);
  do_something_else($@​);
  In the above example, $@​ would be the same when passed to do_something_else

Inline Patch
diff --git a/dist/Locale-Maketext/ChangeLog b/dist/Locale-Maketext/ChangeLog
index 16891a1..7f9b070 100644
--- a/dist/Locale-Maketext/ChangeLog
+++ b/dist/Locale-Maketext/ChangeLog
@@ -6,6 +6,9 @@ Revision history for Perl suite Locale::Maketext
     Fix for CPAN RT #40727: infinite loop in
     Locale::Maketext::Guts::_compile() when working with tainted values
 
+    Fix for CPAN RT #34182: Don't localize $@. 
+    ->maketext calls will now backup and restore $@ so that die messages are not supressed.
+    
 2010−06−22
     * Release 1.15 (included in perl 5.13.3; not released separately)
 
diff --git a/dist/Locale-Maketext/lib/Locale/Maketext.pm b/dist/Locale-Maketext/lib/Locale/Maketext.pm
index 5479a60..5b367ff 100644
--- a/dist/Locale-Maketext/lib/Locale/Maketext.pm
+++ b/dist/Locale-Maketext/lib/Locale/Maketext.pm
@@ -160,12 +160,11 @@ sub failure_handler_auto {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
@@ -195,9 +194,9 @@ sub maketext {
     my($handle, $phrase) = splice(@_,0,2);
     Carp::confess('No handle/phrase') unless (defined($handle) && defined($phrase));
 
-
-    # Don't interefere with $@ in case that's being interpolated into the msg.
-    local $@;
+    # backup $@ in case it it's still being used in the calling code.
+    # If no failures, we'll re-set it back to what it was later.
+    my $at = $@;
 
     # Look up the value:
 
@@ -248,10 +247,12 @@ sub maketext {
             DEBUG and warn "WARNING0: maketext fails looking for <$phrase>\n";
             my $fail;
             if(ref($fail = $handle->{'fail'}) eq 'CODE') { # it's a sub reference
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return &{$fail}($handle, $phrase, @_);
                 # If it ever returns, it should return a good value.
             }
             else { # It's a method name
+                $@ = $at; # Put $@ back in case we altered it along the way.
                 return $handle->$fail($phrase, @_);
                 # If it ever returns, it should return a good value.
             }
@@ -262,8 +263,14 @@ sub maketext {
         }
     }
 
-    return $$value if ref($value) eq 'SCALAR';
-    return $value unless ref($value) eq 'CODE';
+    if(ref($value) eq 'SCALAR'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $$value ;
+    }
+    if(ref($value) ne 'CODE'){
+        $@ = $at; # Put $@ back in case we altered it along the way.
+        return $value ;
+    }
 
     {
         local $SIG{'__DIE__'};
@@ -272,18 +279,19 @@ sub maketext {
     # If we make it here, there was an exception thrown in the
     #  call to $value, and so scream:
     if ($@) {
-        my $err = $@;
         # pretty up the error message
-        $err =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
+        $@ =~ s{\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?}
                  {\n in bracket code [compiled line $1],}s;
         #$err =~ s/\n?$/\n/s;
-        Carp::croak "Error in maketexting \"$phrase\":\n$err as used";
+        Carp::croak "Error in maketexting \"$phrase\":\n$@ as used";
         # Rather unexpected, but suppose that the sub tried calling
         # a method that didn't exist.
     }
     else {
+        $@ = $at; # Put $@ back in case we altered it along the way.
         return $value;
     }
+    $@ = $at; # Put $@ back in case we altered it along the way.
 }
 
 ###########################################################################
@@ -434,10 +442,11 @@ sub _try_use {   # Basically a wrapper around "require Modulename"
     }
 
     DEBUG and warn " About to use $module ...\n";
-    {
-        local $SIG{'__DIE__'};
-        eval "require $module"; # used to be "use $module", but no point in that.
-    }
+
+    local $SIG{'__DIE__'};
+    local $@;
+    eval "require $module"; # used to be "use $module", but no point in that.
+
     if($@) {
         DEBUG and warn "Error using $module \: $@\n";
         return $tried{$module} = 0;
diff --git a/dist/Locale-Maketext/t/30_eval_dollar_at.t b/dist/Locale-Maketext/t/30_eval_dollar_at.t
new file mode 100644
index 0000000..523365d
--- /dev/null
+++ b/dist/Locale-Maketext/t/30_eval_dollar_at.t
@@ -0,0 +1,51 @@
+use strict;
+use warnings;
+
+{
+    package TEST;
+    use base 'Locale::Maketext';
+}
+
+{
+    package TEST::en;
+    use base 'TEST';
+    our %Lexicon = (
+        _AUTO => 1,
+    );
+}
+
+package main;
+use strict;
+use warnings;
+use Test::More tests => 10;
+
+my $lh = TEST->get_handle('en');
+$@ = "foo";
+is($lh->maketext("This works fine"), "This works fine", "straight forward _AUTO string test");
+is($@, "foo", q{$@ isn't altered during calls to maketext});
+
+my $err = eval {
+   $lh->maketext('this is ] an error');
+};
+is($err, undef, "no return from eval");
+like("$@", qr/Unbalanced\s'\]',\sin/ms, '$@ shows that ] was unbalanced');  
+
+# _try_use doesn't pollute $@
+$@ = 'foo2';
+is(Locale::Maketext::_try_use("This::module::does::not::exist"), 0, "0 return if module is missing when _try_use is called");
+is($@, 'foo2', '$@ is unmodified by a failed _try_use');
+
+# _try_use doesn't pollute $@ for valid call
+$@ = '';
+is(Locale::Maketext::_try_use("Locale::Maketext::Guts"), 1, "1 return using valid module Locale::Maketext::Guts");
+is($@, '', '$@ is clean after failed _try_use');
+
+# failure_handler_auto handles $@ locally.
+{
+    $@ = '';
+    my $err = '';
+    $lh->{failure_lex}->{"foo_fail"} = sub {die("fail message");};
+    $err = eval {$lh->failure_handler_auto("foo_fail")};
+    is($err, undef, "die event calling failure_handler on bad code");
+    like($@, qr/^Error in maketexting "foo_fail":/ms, "\$@ is re-written as expected.");
+}
diff --git a/dist/Locale-Maketext/t/30_local.t b/dist/Locale-Maketext/t/30_local.t
deleted file mode 100644
index 23fa2ac..0000000
--- a/dist/Locale-Maketext/t/30_local.t
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/perl -Tw
-
-use strict;
-
-use Test::More tests => 3;
-use Locale::Maketext;
-
-# declare a class...
-{
-  package Woozle;
-  our @ISA = ('Locale::Maketext');
-  our %Lexicon = (
-    _AUTO => 1
-  );
-  keys %Lexicon; # dodges the 'used only once' warning
-}
-
-my $lh = Woozle->new();
-isa_ok($lh, 'Woozle');
-
-$@ = 'foo';
-is($lh->maketext('Eval error: [_1]', $@), 'Eval error: foo', "Make sure \$@ is localized when passed to maketext");
-is($@, 'foo', "\$@ wasn't modified during call");

@p5pRT
Copy link
Author

p5pRT commented Oct 5, 2010

From @cpansprout

On Mon Oct 04 14​:08​:05 2010, toddr@​cpanel.net wrote​:

I agree with you about current code depending on $@​ being localized.

Attached is the diff for the additional code.
<dollar_at.txt>

The revised full patch (patch2.txt) is also attached​:
<patch2.txt>

#p5p just pointed out I accidentally downgraded the version.
Submitting patch3.txt

Thank you. Applied as 9961f4d.

@p5pRT
Copy link
Author

p5pRT commented Oct 5, 2010

@cpansprout - Status changed from 'open' to 'resolved'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant