From bc344123285ead072c87437a15e486ff36fef609 Mon Sep 17 00:00:00 2001 From: Father Chrysostomos Date: Sun, 6 Nov 2011 13:37:55 -0800 Subject: [PATCH] eval"" should reset %^H in more cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is scary: #use sort 'stable'; require re; re->import('/x'); eval ' print "a b" =~ /a b/ ? "ok\n" : "nokay\n"; use re "/m"; print "a b" =~ /a b/ ? "ok\n" : "nokay\n"; '; It prints: ok nokay The re->import statement is supposed to apply to the caller that is currently being compiled, but it makes ‘use re "/m"’ enable /x as well. Uncomment the ‘use sort’ line, and you get: ok ok which is even scarier. eval"" is supposed to compile its argument with the hints under which the eval itself was compiled. Whenever %^H is modified, a flag (HINT_LOCALIZE_HH; LHH hereinafter) is set in $^H. When eval is called, it checks the LHH flag in the hints from the time it was compiled, to determine whether to reset %^H. If LHH is set, it creates a new %^H based on the hints under which it was compiled. Otherwise, it just leaves %^H alone. The problem is that %^H and LHH may be set some time later (re->import), so when the eval runs there is junk in %^H that does not apply to the contents of the eval. There are two layers at which the hints hash is stored. There is the Perl-level hash, %^H, and then there is a faster cop-hints-hash struc- ture underneath. It’s the latter that is actually used during compi- lation. %^H is just a Perl front-end to it. When eval does not reset %^H and %^H has junk in it, the two get out of sync, because eval always sets the cop-hints-hash correctly. Hence the first print in the first example above compiles without ‘use re "/x"’. The ‘use re’ statement after it modifies the %^H-with- junk-in-it, which then gets synchronised with the cop-hints-hash, turning on /x for the next print statement. Adding ‘use sort’ to the top of the program makes the problem go away, because, since sort.pm uses %^H, LHH is set when eval() itself is compiled. This commit fixes this by having pp_entereval check not only the LHH flag from the hints under which it was compiled, but also the hints of the currently compiling code ($^H / PL_hints). --- pp_ctl.c | 6 ++++-- t/op/eval.t | 14 +++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pp_ctl.c b/pp_ctl.c index 7b059dee6e..380caf1b37 100644 --- a/pp_ctl.c +++ b/pp_ctl.c @@ -4132,8 +4132,10 @@ PP(pp_entereval) if (PL_op->op_private & OPpEVAL_HAS_HH) { saved_hh = MUTABLE_HV(SvREFCNT_inc(POPs)); } - else if (PL_op->op_private & OPpEVAL_COPHH - && PL_curcop->cop_hints & HINT_LOCALIZE_HH) { + else if (PL_hints & HINT_LOCALIZE_HH || ( + PL_op->op_private & OPpEVAL_COPHH + && PL_curcop->cop_hints & HINT_LOCALIZE_HH + )) { saved_hh = cop_hints_2hv(PL_curcop, 0); hv_magic(saved_hh, NULL, PERL_MAGIC_hints); } diff --git a/t/op/eval.t b/t/op/eval.t index 49a1ccab41..75da7eceb3 100644 --- a/t/op/eval.t +++ b/t/op/eval.t @@ -6,7 +6,7 @@ BEGIN { require './test.pl'; } -plan(tests => 118); +plan(tests => 119); eval 'pass();'; @@ -567,3 +567,15 @@ for my $k (!0) { is "a" =~ /a/, "1", "string eval leaves readonly lexicals readonly [perl #19135]"; } + +fresh_perl_is(<<'EOP', "ok\nok\nok\n", undef, 'eval clears %^H'); + BEGIN { + require re; re->import('/x'); # should only affect surrounding scope + eval ' + print "a b" =~ /a b/ ? "ok\n" : "nokay\n"; + use re "/m"; + print "a b" =~ /a b/ ? "ok\n" : "nokay\n"; + '; + } + print "ab" =~ /a b/ ? "ok\n" : "nokay\n"; +EOP