if (in_eval) {
I32 cxix;
- exceptsv = sv_2mortal(SvREFCNT_inc_simple_NN(exceptsv));
+ /* We need to keep this SV alive through all the stack unwinding
+ * and FREETMPSing below, while ensuing that it doesn't leak
+ * if we call out to something which then dies (e.g. sub STORE{die}
+ * when unlocalising a tied var). So we do a dance with
+ * mortalising and SAVEFREEing.
+ */
+ sv_2mortal(SvREFCNT_inc_simple_NN(exceptsv));
/*
* Historically, perl used to set ERRSV ($@) early in the die
restartjmpenv = cx->blk_eval.cur_top_env;
restartop = cx->blk_eval.retop;
+
+ /* We need a FREETMPS here to avoid late-called destructors
+ * clobbering $@ *after* we set it below, e.g.
+ * sub DESTROY { eval { die "X" } }
+ * eval { my $x = bless []; die $x = 0, "Y" };
+ * is($@, "Y")
+ * Here the clearing of the $x ref mortalises the anon array,
+ * which needs to be freed *before* $& is set to "Y",
+ * otherwise it gets overwritten with "X".
+ *
+ * However, the FREETMPS will clobber exceptsv, so preserve it
+ * on the savestack for now.
+ */
+ SAVEFREESV(SvREFCNT_inc_simple_NN(exceptsv));
+ FREETMPS;
+ /* now we're about to pop the savestack, so re-mortalise it */
+ sv_2mortal(SvREFCNT_inc_simple_NN(exceptsv));
+
/* Note that unlike pp_entereval, pp_require isn't supposed to
* trap errors. So if we're a require, after we pop the
* CXt_EVAL that pp_require pushed, rethrow the error with
? SvTRUE(*PL_stack_sp)
: PL_stack_sp > oldsp);
- if (gimme == G_VOID)
+ if (gimme == G_VOID) {
PL_stack_sp = oldsp;
+ /* free now to avoid late-called destructors clobbering $@ */
+ FREETMPS;
+ }
else
leave_adjust_stacks(oldsp, oldsp, gimme, 0);
oldsp = PL_stack_base + cx->blk_oldsp;
gimme = cx->blk_gimme;
- if (gimme == G_VOID)
+ if (gimme == G_VOID) {
PL_stack_sp = oldsp;
+ /* free now to avoid late-called destructors clobbering $@ */
+ FREETMPS;
+ }
else
leave_adjust_stacks(oldsp, oldsp, gimme, 1);
CX_LEAVE_SCOPE(cx);
set_up_inc('../lib');
}
-plan(tests => 134);
+plan(tests => 140);
eval 'pass();';
sub { $s; DB::f127786}->();
pass("RT #127786");
}
+
+# Late calling of destructors overwriting $@.
+# When leaving an eval scope (either by falling off the end or dying),
+# we must ensure that any temps are freed before the end of the eval
+# leave: in particular before $@ is set (to either "" or the error),
+# because otherwise the tmps freeing may call a destructor which
+# will change $@ (e.g. due to a successful eval) *after* its been set.
+# Some extra nested scopes are included in the tests to ensure they don't
+# affect the tmps freeing.
+
+{
+ package TMPS;
+ sub DESTROY { eval { die "died in DESTROY"; } } # alters $@
+
+ eval { { 1; { 1; bless []; } } };
+ ::is ($@, "", "FREETMPS: normal try exit");
+
+ eval q{ { 1; { 1; bless []; } } };
+ ::is ($@, "", "FREETMPS: normal string eval exit");
+
+ eval { { 1; { 1; return bless []; } } };
+ ::is ($@, "", "FREETMPS: return try exit");
+
+ eval q{ { 1; { 1; return bless []; } } };
+ ::is ($@, "", "FREETMPS: return string eval exit");
+
+ eval { { 1; { 1; my $x = bless []; die $x = 0, "die in eval"; } } };
+ ::like ($@, qr/die in eval/, "FREETMPS: die try exit");
+
+ eval q{ { 1; { 1; my $x = bless []; die $x = 0, "die in eval"; } } };
+ ::like ($@, qr/die in eval/, "FREETMPS: die eval string exit");
+}