summaryrefslogtreecommitdiff
path: root/op.c
diff options
context:
space:
mode:
authorDavid Mitchell <davem@iabyn.com>2020-02-04 12:23:26 +0000
committerDavid Mitchell <davem@iabyn.com>2020-02-04 12:43:24 +0000
commitd5a02d973b44832f918778202d68f066c8af3963 (patch)
treede83f9de5b8da590e8f7764886f34fe75e892f56 /op.c
parent99c9ca9ebc1ca483167d8c51cdc3bf9e69c08dae (diff)
downloadperl-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.c12
1 files changed, 12 insertions, 0 deletions
diff --git a/op.c b/op.c
index 6e2897b1e0..522b8d2b97 100644
--- a/op.c
+++ b/op.c
@@ -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)