This is a live mirror of the Perl 5 development currently hosted at
eval"" should reset %^H in more cases
authorFather Chrysostomos <>
Sun, 6 Nov 2011 21:37:55 +0000 (13:37 -0800)
committerFather Chrysostomos <>
Sun, 6 Nov 2011 21:57:29 +0000 (13:57 -0800)
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:


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:


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 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).


index 7b059de..380caf1 100644 (file)
--- 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);
index 49a1cca..75da7ec 100644 (file)
@@ -6,7 +6,7 @@ BEGIN {
     require './';
-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";