Skip to content

Commit

Permalink
[perl #119797] Fix if/else in lvalue sub
Browse files Browse the repository at this point in the history
When if/else/unless is the last thing in an lvalue sub, the lvalue
context is not always propagated properly and scope exit tries to
copy things, including arrays, resulting in ‘Bizarre copy of ARRAY’.

This commit fixes the bizarre copy by flagging any leave op that is
part of an lvalue sub’s return sequence, using the OPpLEAVE flag added
for this purpose in the previous commit.  Then pp_leave uses that flag
to avoid copying return values, but protects them via the mortals
stack just like pp_leavesublv (actually pp_ctl.c:S_return_lvalues).

For ‘if’ and ‘unless’ without ‘else’, the lvalue context was not being
propagated, resulting in arrays’ getting flattened despite the lvalue
context.  op_lvalue_flags in op.c needed to handle AND and OR ops,
which ‘if’ and ‘unless’ compile to, to make this work.
  • Loading branch information
Father Chrysostomos committed Oct 24, 2013
1 parent 4c3ed74 commit 2ec7f6f
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 14 deletions.
3 changes: 2 additions & 1 deletion embed.fnc
Expand Up @@ -1955,7 +1955,8 @@ sR |PerlIO *|check_type_and_open|NN SV *name
#ifndef PERL_DISABLE_PMC
sR |PerlIO *|doopen_pm |NN SV *name
#endif
s |SV ** |adjust_stack_on_leave|NN SV **newsp|NN SV **sp|NN SV **mark|I32 gimme|U32 flags
s |SV ** |adjust_stack_on_leave|NN SV **newsp|NN SV **sp|NN SV **mark|I32 gimme \
|U32 flags|bool lvalue
iRn |bool |path_is_searchable|NN const char *name
sR |I32 |run_user_filter|int idx|NN SV *buf_sv|int maxlen
sR |PMOP* |make_matcher |NN REGEXP* re
Expand Down
2 changes: 1 addition & 1 deletion embed.h
Expand Up @@ -1516,7 +1516,7 @@
#define refto(a) S_refto(aTHX_ a)
# endif
# if defined(PERL_IN_PP_CTL_C)
#define adjust_stack_on_leave(a,b,c,d,e) S_adjust_stack_on_leave(aTHX_ a,b,c,d,e)
#define adjust_stack_on_leave(a,b,c,d,e,f) S_adjust_stack_on_leave(aTHX_ a,b,c,d,e,f)
#define check_type_and_open(a) S_check_type_and_open(aTHX_ a)
#define destroy_matcher(a) S_destroy_matcher(aTHX_ a)
#define do_smartmatch(a,b,c) S_do_smartmatch(aTHX_ a,b,c)
Expand Down
11 changes: 10 additions & 1 deletion op.c
Expand Up @@ -2252,8 +2252,9 @@ Perl_op_lvalue_flags(pTHX_ OP *o, I32 type, U32 flags)
PL_modcount++;
break;

case OP_SCOPE:
case OP_LEAVE:
o->op_private |= OPpLVALUE;
case OP_SCOPE:
case OP_ENTER:
case OP_LINESEQ:
localize = 0;
Expand Down Expand Up @@ -2288,6 +2289,14 @@ Perl_op_lvalue_flags(pTHX_ OP *o, I32 type, U32 flags)

case OP_COREARGS:
return o;

case OP_AND:
case OP_OR:
if (type == OP_LEAVESUBLV) {
op_lvalue(cLOGOPo->op_first, type);
op_lvalue(cLOGOPo->op_first->op_sibling, type);
}
goto nomod;
}

/* [20011101.069] File test operators interpret OPf_REF to mean that
Expand Down
32 changes: 23 additions & 9 deletions pp_ctl.c
Expand Up @@ -2033,8 +2033,13 @@ PP(pp_dbstate)
return NORMAL;
}

/* SVs on the stack that have any of the flags passed in are left as is.
Other SVs are protected via the mortals stack if lvalue is true, and
copied otherwise. */

STATIC SV **
S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme, U32 flags)
S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme,
U32 flags, bool lvalue)
{
bool padtmp = 0;
PERL_ARGS_ASSERT_ADJUST_STACK_ON_LEAVE;
Expand All @@ -2046,7 +2051,10 @@ S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme, U32 fla
if (gimme == G_SCALAR) {
if (MARK < SP)
*++newsp = ((SvFLAGS(*SP) & flags) || (padtmp && SvPADTMP(*SP)))
? *SP : sv_mortalcopy(*SP);
? *SP
: lvalue
? sv_2mortal(SvREFCNT_inc_simple_NN(*SP))
: sv_mortalcopy(*SP);
else {
/* MEXTEND() only updates MARK, so reuse it instead of newsp. */
MARK = newsp;
Expand All @@ -2061,7 +2069,9 @@ S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme, U32 fla
if ((SvFLAGS(*MARK) & flags) || (padtmp && SvPADTMP(*MARK)))
*++newsp = *MARK;
else {
*++newsp = sv_mortalcopy(*MARK);
*++newsp = lvalue
? sv_2mortal(SvREFCNT_inc_simple_NN(*MARK))
: sv_mortalcopy(*MARK);
TAINT_NOT; /* Each item is independent */
}
}
Expand Down Expand Up @@ -2104,7 +2114,8 @@ PP(pp_leave)
gimme = OP_GIMME(PL_op, (cxstack_ix >= 0) ? gimme : G_SCALAR);

TAINT_NOT;
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP);
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP,
PL_op->op_private & OPpLVALUE);
PL_curpm = newpm; /* Don't pop $1 et al till now */

LEAVE_with_name("block");
Expand Down Expand Up @@ -2266,7 +2277,7 @@ PP(pp_leaveloop)
newsp = PL_stack_base + cx->blk_loop.resetsp;

TAINT_NOT;
SP = adjust_stack_on_leave(newsp, SP, MARK, gimme, 0);
SP = adjust_stack_on_leave(newsp, SP, MARK, gimme, 0, FALSE);
PUTBACK;

POPLOOP(cx); /* Stack values are safe: release loop vars ... */
Expand Down Expand Up @@ -4315,7 +4326,7 @@ PP(pp_leaveeval)

TAINT_NOT;
SP = adjust_stack_on_leave((gimme == G_VOID) ? SP : newsp, SP, newsp,
gimme, SVs_TEMP);
gimme, SVs_TEMP, FALSE);
PL_curpm = newpm; /* Don't pop $1 et al till now */

#ifdef DEBUGGING
Expand Down Expand Up @@ -4413,7 +4424,8 @@ PP(pp_leavetry)
PERL_UNUSED_VAR(optype);

TAINT_NOT;
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP);
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme,
SVs_PADTMP|SVs_TEMP, FALSE);
PL_curpm = newpm; /* Don't pop $1 et al till now */

LEAVE_with_name("eval_scope");
Expand Down Expand Up @@ -4459,7 +4471,8 @@ PP(pp_leavegiven)
assert(CxTYPE(cx) == CXt_GIVEN);

TAINT_NOT;
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP);
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme,
SVs_PADTMP|SVs_TEMP, FALSE);
PL_curpm = newpm; /* Don't pop $1 et al till now */

LEAVE_with_name("given");
Expand Down Expand Up @@ -5037,7 +5050,8 @@ PP(pp_leavewhen)
assert(CxTYPE(cx) == CXt_WHEN);

TAINT_NOT;
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme, SVs_PADTMP|SVs_TEMP);
SP = adjust_stack_on_leave(newsp, SP, newsp, gimme,
SVs_PADTMP|SVs_TEMP, FALSE);
PL_curpm = newpm; /* pop $1 et al */

LEAVE_with_name("when");
Expand Down
2 changes: 1 addition & 1 deletion proto.h
Expand Up @@ -6241,7 +6241,7 @@ PERL_CALLCONV GV* Perl_softref2xv(pTHX_ SV *const sv, const char *const what, co

#endif
#if defined(PERL_IN_PP_CTL_C)
STATIC SV ** S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme, U32 flags)
STATIC SV ** S_adjust_stack_on_leave(pTHX_ SV **newsp, SV **sp, SV **mark, I32 gimme, U32 flags, bool lvalue)
__attribute__nonnull__(pTHX_1)
__attribute__nonnull__(pTHX_2)
__attribute__nonnull__(pTHX_3);
Expand Down
38 changes: 37 additions & 1 deletion t/op/sub_lval.t
Expand Up @@ -3,7 +3,7 @@ BEGIN {
@INC = '../lib';
require './test.pl';
}
plan tests=>193;
plan tests=>201;

sub a : lvalue { my $a = 34; ${\(bless \$a)} } # Return a temporary
sub b : lvalue { ${\shift} }
Expand Down Expand Up @@ -972,3 +972,39 @@ for (sub : lvalue { "$x" }->()) {
eval { &{\&utf8::is_utf8}("") = 3 };
like $@, qr/^Can't modify non-lvalue subroutine call at /,
'XSUB not seen at compile time dies in lvalue context';

# [perl #119797] else implicitly returning value
# This used to cause Bizarre copy of ARRAY in pp_leave
sub else119797 : lvalue {
if ($_[0]) {
1; # two statements force a leave op
@119797
}
else {
@119797
}
}
eval { (else119797(0)) = 1..3 };
is $@, "", '$@ after writing to array returned by else';
is "@119797", "1 2 3", 'writing to array returned by else';
eval { (else119797(1)) = 4..6 };
is $@, "", '$@ after writing to array returned by if (with else)';
is "@119797", "4 5 6", 'writing to array returned by if (with else)';
sub if119797 : lvalue {
if ($_[0]) {
@119797
}
}
@119797 = ();
eval { (if119797(1)) = 4..6 };
is $@, "", '$@ after writing to array returned by if';
is "@119797", "4 5 6", 'writing to array returned by if';
sub unless119797 : lvalue {
unless ($_[0]) {
@119797
}
}
@119797 = ();
eval { (unless119797(0)) = 4..6 };
is $@, "", '$@ after writing to array returned by unless';
is "@119797", "4 5 6", 'writing to array returned by unless';

0 comments on commit 2ec7f6f

Please sign in to comment.