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

eval: DESTROY may overwrite $@ - exceptions are not handled #5965

Closed
p5pRT opened this issue Sep 28, 2002 · 13 comments
Closed

eval: DESTROY may overwrite $@ - exceptions are not handled #5965

p5pRT opened this issue Sep 28, 2002 · 13 comments

Comments

@p5pRT
Copy link

p5pRT commented Sep 28, 2002

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

Searchable as RT17650$

@p5pRT
Copy link
Author

p5pRT commented Sep 28, 2002

From burkhard.meier@gmx.de

Created by burkhard.e.f.meier@t-online.de

This is a bug report for perl from burkhard.e.f.meier@​t-online.de,
generated with the help of perlbug 1.34 running under perl v5.8.0.

-----------------------------------------------------------------
Executing the following script...

eval {
  my $x = new SomeClass;
  die;
};
if( $@​ ) {
  print "Caught exception​: $@​\n";
}
else {
  print "No error has occurred!\n";
}

package SomeClass;

sub new
{
  my $self = {};
  bless $self;
}

sub DESTROY
{
  print "destroying object\n";
  print "\$\@​​: '$@​'\n";
  eval { 1; };
  print "\$\@​​: '$@​'\n";
}

......results in the output​:

G​:\tmp>perl -w eval.pl
destroying object
$@​​: 'Died at eval.pl line 3.
'
$@​​: ''
No error has occurred!

It seems that "die" first sets $@​, then the local objects' destructors are
called.
Since SomeClass's DESTROY performs an eval, $@​ is overwritten before
the enclosing scope gets any chance to handle it. This behavior seems
very weird to me. Is this really intended? At least it can lead to
software errors that are hard to track down (real-world example​:
the CPAN module Win32​::TieRegistry). This is really a trap for
both the module authors and their users.
I think that after a "die", $@​ should be set only after all
local objects' destructors have been called. Why isn't it done this way?

Perl Info

Flags:
    category=core
    severity=medium

Site configuration information for perl v5.8.0:

Configured by burkhard at Sat Sep 28 15:33:39 2002.

Summary of my perl5 (revision 5 version 8 subversion 0) configuration:
  Platform:
    osname=MSWin32, osvers=4.0, archname=MSWin32-x86-multi-thread
    uname=''
    config_args='undef'
    hint=recommended, useposix=true, d_sigaction=undef
    usethreads=undef use5005threads=undef useithreads=define
usemultiplicity=define
    useperlio=define d_sfio=undef uselargefiles=undef usesocks=undef
    use64bitint=undef use64bitall=undef uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='cl', ccflags
='-nologo -Gf -W3 -MD -DNDEBUG -O1 -DWIN32 -D_CONSOLE -DNO_STRICT   -DPERL_I
MPLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -DPERL_MSVCRT_READFIX',
    optimize='-MD -DNDEBUG -O1',
    cppflags='-DWIN32'
    ccversion='', gccversion='', gccosandvers=''
    intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
    d_longlong=undef, longlongsize=8, d_longdbl=define, longdblsize=10
    ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t',
lseeksize=4
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='link', ldflags
'-nologo -nodefaultlib -release  -libpath:"g:\perl5.8.0\lib\CORE"  -machine:
x86'
    libpth=\lib
    libs=  oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib  netapi32.lib
uuid.lib wsock32.lib mpr.lib winmm.lib  version.lib odbc32.lib odbccp32.lib
msvcrt.lib
    perllibs=  oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib  netapi32.lib
uuid.lib wsock32.lib mpr.lib winmm.lib  version.lib odbc32.lib odbccp32.lib
msvcrt.lib
    libc=msvcrt.lib, so=dll, useshrplib=yes, libperl=perl58.lib
    gnulibc_version='undef'
  Dynamic Linking:
    dlsrc=dl_win32.xs, dlext=dll, d_dlsymun=undef, ccdlflags=' '
    cccdlflags=' ',
ddlflags='-dll -nologo -nodefaultlib -release  -libpath:"g:\perl5.8.0\lib\CO
RE"  -machine:x86'

Locally applied patches:



@INC for perl v5.8.0:
    g:/perl5.8.0/lib
    g:/perl5.8.0/site/lib
    .


Environment for perl v5.8.0:
    HOME=G:/
    LANG (unset)
    LANGUAGE (unset)
    LD_LIBRARY_PATH (unset)
    LOGDIR (unset)
    PATH=g:\perl5.8.0\bin;g:\jdk1.2.2\bin;g:\program
files\perl5.8.0\bin;G:\Perl5.6.1\bin\;g:\jdk1.1.6\bin;g:\mksnt;g:\winnt\syst
em32;g:\winnt;g:\winnt\system32\wbem;g:\perl\bin;g:\ultrae~1;;g:\program
files\perforce;g:\mssql7\binn;d:\ultrae~1;;G:\IXOS-ARCHIVE\OPT\FTSE\_nti40\d
ll;g:\ixos-a~1\bin;G:\IXOS-ARCHIVE\BIN;F:\IXOS\ADMS\DEBUG\BIN;F:\IXOS\ADMS\D
EBUG\LIB;G:\DEVSTUDIO\COMMON\TOOLS\WINNT;G:\DEVSTUDIO\COMMON\MSDEV98\BIN;G:\
DEVSTUDIO\COMMON\TOOLS;G:\DEVSTUDIO\VC98\BIN;G:\KAWA30;G:\SUPERC~1\Bin;G:\Pr
ogram Files\IrfanView;D:\ULTRAE~1;
    PERL_BADLANG (unset)
    SHELL=g:/mksnt/sh.exe


@p5pRT
Copy link
Author

p5pRT commented Sep 30, 2002

From @eserte

Burkhard Meier (via RT) <perlbug@​perl.org> writes​:

# New Ticket Created by Burkhard Meier
# Please include the string​: [perl #17650]
# in the subject line of all future correspondence about this issue.
# <URL​: http​://rt.perl.org/rt2/Ticket/Display.html?id=17650 >

This is a bug report for perl from burkhard.e.f.meier@​t-online.de,
generated with the help of perlbug 1.34 running under perl v5.8.0.

-----------------------------------------------------------------
Executing the following script...

eval {
my $x = new SomeClass;
die;
};
if( $@​ ) {
print "Caught exception​: $@​\n";
}
else {
print "No error has occurred!\n";
}

package SomeClass;

sub new
{
my $self = {};
bless $self;
}

sub DESTROY
{
print "destroying object\n";
print "\$\@​​: '$@​'\n";
eval { 1; };
print "\$\@​​: '$@​'\n";
}

.......results in the output​:

G​:\tmp>perl -w eval.pl
destroying object
$@​​: 'Died at eval.pl line 3.
'
$@​​: ''
No error has occurred!

It seems that "die" first sets $@​, then the local objects' destructors are
called.
Since SomeClass's DESTROY performs an eval, $@​ is overwritten before
the enclosing scope gets any chance to handle it. This behavior seems
very weird to me. Is this really intended? At least it can lead to
software errors that are hard to track down (real-world example​:
the CPAN module Win32​::TieRegistry). This is really a trap for
both the module authors and their users.
I think that after a "die", $@​ should be set only after all
local objects' destructors have been called. Why isn't it done this way?

I think the DESTROY method is responsible for preserving the value of
$@​ --- either by appending the new $@​ value to the old one or by
localizing $@​.

Regards,
  Slaven

--
Slaven Rezic - slaven.rezic@​berlin.de

  Berlin Perl Mongers - http​://berliner.pm.org

@p5pRT
Copy link
Author

p5pRT commented Sep 30, 2002

From burkhard.meier@gmx.de

I think the DESTROY method is responsible for preserving the value of
$@​ --- either by appending the new $@​ value to the old one or by
localizing $@​.

Come on, that's just a workaround... And who knows about this?
It's just to easy to fall into this trap, I think. Even if your object does
it correctly - the way you propose, you can never be sure about the behavior
of any
objects or functions you are just using within your eval or your DESTROY
method. My personal conclusion is not to use eval anymore the way I did it
before.

BTW, does anybody if this specific problem will also occur in Perl6?

Regards,
  Burkhard

@p5pRT
Copy link
Author

p5pRT commented Jul 8, 2005

From alex@chmrr.net

Heya,
  I got bitten by this bug today. I'm not altogether satisfied by the
last and only comment on the bug, which summarizes to "but there's a
workaround, so it's not perl's fault!"
  Attached is a patch which restores $@​ after any DESTROY blocks have
run.
- Alex

--
Networking -- only one letter away from not working

@p5pRT
Copy link
Author

p5pRT commented Jul 8, 2005

From alex@chmrr.net

perl-destroy.patch
diff -ru perl-current/pp_ctl.c perl-patched/pp_ctl.c
--- perl-current/pp_ctl.c	2005-07-08 13:03:02.000000000 -0400
+++ perl-patched/pp_ctl.c	2005-07-08 15:39:59.000000000 -0400
@@ -1432,6 +1432,7 @@
 	    else {
 		sv_setpvn(ERRSV, message, msglen);
 	    }
+	    message = SvPV_const(ERRSV, msglen);
 	}
 
 	while ((cxix = dopoptoeval(cxstack_ix)) < 0
@@ -1471,6 +1472,12 @@
 	     * minimal fix --GSAR */
 	    PL_curcop = cx->blk_oldcop;
 
+	    /* Leaving the block could run DESTROY blocks which might
+	     * have altered $@; reset it here just in case */
+	    if (message) {
+		sv_setpvn(ERRSV, message, msglen);
+	    }
+
 	    if (optype == OP_REQUIRE) {
                 const char* msg = SvPVx_nolen_const(ERRSV);
 		SV * const nsv = cx->blk_eval.old_namesv;
diff -ru perl-current/t/op/die.t perl-patched/t/op/die.t
--- perl-current/t/op/die.t	2005-07-06 16:38:38.000000000 -0400
+++ perl-patched/t/op/die.t	2005-07-08 15:42:12.000000000 -0400
@@ -1,6 +1,6 @@
 #!./perl
 
-print "1..15\n";
+print "1..16\n";
 
 $SIG{__DIE__} = sub { print ref($_[0]) ? ("ok ",$_[0]->[0]++,"\n") : @_ } ;
 
@@ -72,3 +72,27 @@
     print "not " unless $ok;
     print "ok 15\n";
 }
+
+# [perl #17650] DESTROY can unset $@
+{
+  package SomeClass;
+
+  sub new {
+    my $self = {};
+    bless $self;
+  }
+
+  sub DESTROY {
+    eval { 1; };
+  }
+}
+
+{
+  local $SIG{__DIE__};
+  eval {
+    my $x = new SomeClass;
+    die;
+  };
+  print($@ ? "ok 16\n" : "not ok 16\n");
+}
+

@p5pRT
Copy link
Author

p5pRT commented Jul 10, 2005

From @iabyn

On Fri, Jul 08, 2005 at 03​:59​:34PM -0400, Alex Vandiver wrote​:

Heya,
I got bitten by this bug today. I'm not altogether satisfied by the
last and only comment on the bug, which summarizes to "but there's a
workaround, so it's not perl's fault!"
Attached is a patch which restores $@​ after any DESTROY blocks have

I don't think this fix is robust. You save the current PV value of ERRSV,
and then later set it back; in the meantime, ERRSV's PV may have been
realloced, and message now points to free or realloced garbage.

--
There's a traditional definition of a shyster​: a lawyer who, when the law
is against him, pounds on the facts; when the facts are against him,
pounds on the law; and when both the facts and the law are against him,
pounds on the table.
  -- Eben Moglen referring to SCO

@p5pRT
Copy link
Author

p5pRT commented Jul 18, 2005

From alex@chmrr.net

On Sun, 2005-07-10 at 14​:29 +0100, Dave Mitchell wrote​:

I don't think this fix is robust. You save the current PV value of ERRSV,
and then later set it back; in the meantime, ERRSV's PV may have been
realloced, and message now points to free or realloced garbage.
Point. The attached patch uses save_item(ERRSV) to fully localize $@​.
Also added more tests that the previous patch didn't pass on. Thoughts?
- Alex

--
Networking -- only one letter away from not working

@p5pRT
Copy link
Author

p5pRT commented Jul 18, 2005

From alex@chmrr.net

perl-destroy.patch
diff -ru perl-current/sv.c perl-patched/sv.c
--- perl-current/sv.c	2005-07-08 13:03:02.000000000 -0400
+++ perl-patched/sv.c	2005-07-18 00:12:01.000000000 -0400
@@ -5519,6 +5519,10 @@
 		    PUSHMARK(SP);
 		    PUSHs(tmpref);
 		    PUTBACK;
+
+		    /* Localise $@ so DESTROY caused by die can't reset $@ */
+		    save_item(ERRSV); 
+
 		    call_sv((SV*)destructor, G_DISCARD|G_EVAL|G_KEEPERR|G_VOID);
 		
 		
diff -ru perl-current/t/op/die.t perl-patched/t/op/die.t
--- perl-current/t/op/die.t	2005-07-06 16:38:38.000000000 -0400
+++ perl-patched/t/op/die.t	2005-07-16 02:25:44.000000000 -0400
@@ -1,6 +1,6 @@
 #!./perl
 
-print "1..15\n";
+print "1..17\n";
 
 $SIG{__DIE__} = sub { print ref($_[0]) ? ("ok ",$_[0]->[0]++,"\n") : @_ } ;
 
@@ -72,3 +72,43 @@
     print "not " unless $ok;
     print "ok 15\n";
 }
+
+# [perl #17650] DESTROY can unset $@
+{
+  package SomeClass;
+  sub new { return bless {}; }
+  sub DESTROY {
+    eval { 1; };
+  }
+}
+
+{
+  package SomeOtherClass;
+  sub new { return bless {}; }
+  sub DESTROY {
+    eval { die bless {}, "Moose"; }
+  }
+}
+
+{
+  local $SIG{__DIE__};
+  eval {
+    my $x = new SomeClass;
+    die;
+  };
+  print($@ ? "ok 16\n" : "not ok 16\n");
+
+  eval {
+    my $x = new SomeOtherClass;
+    die bless {}, "Thingy";
+  };
+  print($@ ? "ok 17\n" : "not ok 17\n");
+  print(ref $@ eq "Thingy" ? "ok 18\n" : "not ok 18\n");
+
+  eval {
+    my $s = new SomeOtherClass;
+  };
+  print($@ ? "not ok 19\n" : "ok 19\n");
+}
+
+

@p5pRT
Copy link
Author

p5pRT commented Jul 19, 2005

From @iabyn

On Mon, Jul 18, 2005 at 01​:14​:43AM -0400, Alex Vandiver wrote​:

On Sun, 2005-07-10 at 14​:29 +0100, Dave Mitchell wrote​:

I don't think this fix is robust. You save the current PV value of ERRSV,
and then later set it back; in the meantime, ERRSV's PV may have been
realloced, and message now points to free or realloced garbage.
Point. The attached patch uses save_item(ERRSV) to fully localize $@​.
Also added more tests that the previous patch didn't pass on. Thoughts?
- Alex

I like the idea of localising $@​ before calling DESTROY, but unfortunately
it breaks this test​:

  lib/warnings..............................PROG​:
  # pp_ctl.c
  use warnings 'misc' ;
  package Foo;
  DESTROY { die "@​{$_[0]} foo bar" }
  { bless ['A'], 'Foo' for 1..10 }
  { bless ['B'], 'Foo' for 1..10 }
  EXPECTED​:
  (in cleanup) A foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  GOT​:
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) A foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  (in cleanup) B foo bar at - line 4.
  # Failed at ../lib/warnings.t line 186

The behaviour being tested here is that a die in a DESTROY gets converted
to a warn with an (in cleanup) prefix, and that subsequent massages with
the same suffix should be suppressed. The current supression mechanism
relies on comparing the new message with the current value of ERRSV.

I'm not really sure offhand of the best way round this. Perhaps have an
extra PL_ var that holds the hash of the last (in cleanup) warning ???

--
My Dad used to say 'always fight fire with fire', which is probably why
he got thrown out of the fire brigade.

@p5pRT
Copy link
Author

p5pRT commented Jul 19, 2005

From @nwc10

On Tue, Jul 19, 2005 at 01​:19​:19AM +0100, Dave Mitchell wrote​:

On Mon, Jul 18, 2005 at 01​:14​:43AM -0400, Alex Vandiver wrote​:

On Sun, 2005-07-10 at 14​:29 +0100, Dave Mitchell wrote​:

I don't think this fix is robust. You save the current PV value of ERRSV,
and then later set it back; in the meantime, ERRSV's PV may have been
realloced, and message now points to free or realloced garbage.
Point. The attached patch uses save_item(ERRSV) to fully localize $@​.
Also added more tests that the previous patch didn't pass on. Thoughts?
- Alex

I like the idea of localising $@​ before calling DESTROY, but unfortunately
it breaks this test​:

Which means that it wasn't tested before being sent to the list.

  make test

exists for a reason.

Nicholas Clark

@p5pRT
Copy link
Author

p5pRT commented Aug 18, 2005

From guest@guest.guest.xxxxxxxx

I think the DESTROY method is responsible for preserving the value of
$@​ --- either by appending the new $@​ value to the old one or by
localizing $@​.

There's another problem with this work-around -- it breaks in the other
direction!

tin-foil​:/tmp glasser$ cat /tmp/localdie
sub i_die {
  die "this is why I died";
}

sub i_localize_dollar_at { # This could certainly be DESTROY
  local $@​;
  i_die();
}

eval { i_localize_dollar_at() };

my $error = $@​;

if ($error) {
  print "The error was​: $error\n";
} else {
  print "There was no error. (Or was there?)\n";
}
tin-foil​:/tmp glasser$ perl localdie
There was no error. (Or was there?)

That is, localizing $@​ in DESTROY solves the problem of "an eval inside
the DESTROY hides an error that happened before the DESTROY", but it
causes the problem that it hides an error that happens *in* the DESTROY!

I'm not sure if Alex's patch has this issue too.

--dave

@p5pRT
Copy link
Author

p5pRT commented Feb 6, 2006

From @smpeters

[burkhard - Sat Sep 28 07​:48​:04 2002]​:

This is a bug report for perl from burkhard.e.f.meier@​t-online.de,
generated with the help of perlbug 1.34 running under perl v5.8.0.

-----------------------------------------------------------------
Executing the following script...

eval {
my $x = new SomeClass;
die;
};
if( $@​ ) {
print "Caught exception​: $@​\n";
}
else {
print "No error has occurred!\n";
}

package SomeClass;

sub new
{
my $self = {};
bless $self;
}

sub DESTROY
{
print "destroying object\n";
print "\$\@​​: '$@​'\n";
eval { 1; };
print "\$\@​​: '$@​'\n";
}

......results in the output​:

G​:\tmp>perl -w eval.pl
destroying object
$@​​: 'Died at eval.pl line 3.
'
$@​​: ''
No error has occurred!

It seems that "die" first sets $@​, then the local objects' destructors are
called.
Since SomeClass's DESTROY performs an eval, $@​ is overwritten before
the enclosing scope gets any chance to handle it. This behavior seems
very weird to me. Is this really intended? At least it can lead to
software errors that are hard to track down (real-world example​:
the CPAN module Win32​::TieRegistry). This is really a trap for
both the module authors and their users.
I think that after a "die", $@​ should be set only after all
local objects' destructors have been called. Why isn't it done this way?

This bug has been resolved by documenting this behavior with a patch in
ticket #38034.

@p5pRT
Copy link
Author

p5pRT commented Feb 6, 2006

@smpeters - 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