diff options
author | David Mitchell <davem@iabyn.com> | 2020-02-04 12:23:26 +0000 |
---|---|---|
committer | David Mitchell <davem@iabyn.com> | 2020-02-04 12:43:24 +0000 |
commit | d5a02d973b44832f918778202d68f066c8af3963 (patch) | |
tree | de83f9de5b8da590e8f7764886f34fe75e892f56 /op.c | |
parent | 99c9ca9ebc1ca483167d8c51cdc3bf9e69c08dae (diff) | |
download | perl-d5a02d973b44832f918778202d68f066c8af3963.tar.gz |
multiconcat: keep assign for 'local $foo = "..."'
In something like
local $~ = "$~X";
i.e. where localising a magic variable whose previous value should be
used as part of a string concat on the RHS, don't fold the assign into
the multiconcat op. Otherwise the code execution path looks a bit like:
local($~) = undef;
multiconcat($~, $~, "X");
[ where multiconcat's args are (target, arg1, arg2,....) ]
and thus multiconcat sees an undef arg.
By leaving the assign out of the multiconcat, code execution now looks
like
my $targ;
multiconcat($targ, $~, "X");
local($~) = $targ;
See http://nntp.perl.org/group/perl.perl5.porters/256898,
"Bug in format introduced in 5.27.6".
Although the bug only appears with magic vars, this patch pessimises
all forms of 'local $foo = "..."', 'local $foo{bar} = "..."' etc.
Strictly speaking the bug occurs because with 'local' you end up with
two SVs (the saved one and the one currently in the glob) which both
have the same container magic and where mg_set()ing one changes the
mg_get() value of the other. Thus, vars like $!. One of the two SVs
becomes an arg of multiconcat, the other becomes its target. Part of
localising the target SV (before multiconcat is called) wipes the value
of the arg SV.
Diffstat (limited to 'op.c')
-rw-r--r-- | op.c | 12 |
1 files changed, 12 insertions, 0 deletions
@@ -2915,6 +2915,18 @@ S_maybe_multiconcat(pTHX_ OP *o) targetop = OpSIBLING(topop); if (!targetop) /* probably some sort of syntax error */ return; + + /* don't optimise away assign in 'local $foo = ....' */ + if ( (targetop->op_private & OPpLVAL_INTRO) + /* these are the common ops which do 'local', but + * not all */ + && ( targetop->op_type == OP_GVSV + || targetop->op_type == OP_RV2SV + || targetop->op_type == OP_AELEM + || targetop->op_type == OP_HELEM + ) + ) + return; } else if ( topop->op_type == OP_CONCAT && (topop->op_flags & OPf_STACKED) |