Skip to content

Commit 39d060c

Browse files
committed
propagate correct ref context to both ?: branches
GH #18669 In something like @{ expr } = ... the expression is expected to return an array ref. If the expression is something like $h{foo}, then the helem op needs to know both that: - it is in lvalue context, so should autovivify the foo element if not present; - it is in array ref context, so it should autovivify the value to an empty array ref, rather than just to undef. The function Perl_doref() is used to propagate this ref context at compile time, e.g. by setting the OPf_MOD and OPpDEREF_AV flags on the OP_HELEM op. My commit v5.31.1-87-ge9b0092a10 made this function non-recursive (so that deep expressions wouldn't SEGV during compilation), but introduced a bug when the expression included the ternary condition operator, '?:'. In particular, since '?:' is the only OP where doref() needs to recurse down *two* branches, I made the function just iterate down the tree, and then have special handling for OP_COND_EXPR. This involved, once having finished iterating down the tree, to work back up the tree looking for OP_COND_EXPR nodes, and if found, iterate back down the second branch. This had a fatal flaw: a 'type' variable indicated what context to apply. For example in @{$h{expr}} = ..., type would start off as OP_RV2AV, but as the tree was walked, would change to OP_HELEM and then to OP_RV2HV. When walking back up the tree, this value wasn't being restored. The specific bug in the ticket boiled down to something like @{ $cond ? $h{p} : $h{q} } = ...; where the correct OPpDEREF_AV flag was being set on the first helem op, but an incorrect OPpDEREF_HV on the second. Since I can't think of anything better, the fix in this commit restores some limited recursion to doref(). Namely, for an OP_COND_EXPR op, it now recurses down that op's first branch, then after it returns, iterates as normal down the second branch. Thus extremely deeply nested ternary code like: @{ $c1 ? $c2 ? $c3 ? .... } ... could start to SEGV during compilation again.
1 parent 357c161 commit 39d060c

File tree

3 files changed

+53
-51
lines changed

3 files changed

+53
-51
lines changed

op.c

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3774,7 +3774,13 @@ Perl_doref(pTHX_ OP *o, I32 type, bool set_op_ref)
37743774
break;
37753775

37763776
case OP_COND_EXPR:
3777+
/* OP_COND_EXPR is the only op where we have to propagate
3778+
* context to *both* branches. Recurse on the first branch,
3779+
* then iterate on the second branch.
3780+
*/
37773781
o = OpSIBLING(cUNOPo->op_first);
3782+
doref(o, type, set_op_ref);
3783+
o = OpSIBLING(o);
37783784
continue;
37793785

37803786
case OP_RV2SV:
@@ -3847,22 +3853,12 @@ Perl_doref(pTHX_ OP *o, I32 type, bool set_op_ref)
38473853
break;
38483854
} /* switch */
38493855

3850-
while (1) {
3851-
if (o == top_op)
3852-
return scalar(top_op); /* at top; no parents/siblings to try */
3853-
if (OpHAS_SIBLING(o)) {
3854-
o = o->op_sibparent;
3855-
/* Normally skip all siblings and go straight to the parent;
3856-
* the only op that requires two children to be processed
3857-
* is OP_COND_EXPR */
3858-
if (!OpHAS_SIBLING(o)
3859-
&& o->op_sibparent->op_type == OP_COND_EXPR)
3860-
break;
3861-
continue;
3862-
}
3863-
o = o->op_sibparent; /* try parent's next sibling */
3864-
}
3856+
/* whole tree has been scanned for ref stuff; now propagate
3857+
* scalar context */
3858+
return scalar(top_op);
3859+
38653860
} /* while */
3861+
38663862
}
38673863

38683864

t/op/ref.t

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ BEGIN {
88

99
use strict qw(refs subs);
1010

11-
plan(257);
11+
plan(265);
1212

1313
# Test this first before we extend the stack with other operations.
1414
# This caused an asan failure due to a bad write past the end of the stack.
@@ -913,6 +913,47 @@ EOF
913913
'rt#130861: heap uaf in pp_rv2sv');
914914
}
915915

916+
# GH 18669
917+
# The correct autovivification lvalue ref context should be propagated to
918+
# both branches of a ?:. So in something like:
919+
# @{ $cond ? $h{a} : $h{b} } = ...;
920+
# the helem ops on *both* sides of the conditional should get the DREFAV
921+
# flag set, indicating that if the hash element doesn't exist, it should
922+
# be autovivified as an *array ref*.
923+
#
924+
925+
{
926+
my $x = { arr => undef };
927+
eval {
928+
push(@{ $x->{ decide } ? $x->{ not_here } : $x->{ new } }, "mana");
929+
};
930+
931+
is($@, "", "GH 18669: push on non-existent hash ref entry: no errors");
932+
is(eval {$x->{new}[0] }, 'mana',
933+
"GH 18669: push on non-existent hash ref entry: autovivifies"
934+
);
935+
936+
$x = { arr => undef };
937+
eval {
938+
push(@{ $x->{ decide } ? $x->{ not_here } : $x->{ arr } }, "mana");
939+
};
940+
941+
is($@, "", "GH 18669: push on undef hash ref entry: no errors");
942+
is(eval { $x->{arr}[0] }, 'mana',
943+
"GH 18669: push on undef hash ref entry: autovivifies"
944+
);
945+
946+
# try both branches
947+
for my $cond (0, 1) {
948+
my %h;
949+
eval { @{ $cond ? $h{p} : $h{q} } = 99; };
950+
is($@, "", "GH 18669: array assign on $cond cond: no errors");
951+
is($h{$cond ? 'p' : 'q'}[0], 99,
952+
"GH 18669: array assign on $cond cond: autovivifies"
953+
);
954+
}
955+
}
956+
916957
# Bit of a hack to make test.pl happy. There are 3 more tests after it leaves.
917958
$test = curr_test();
918959
curr_test($test + 3);

t/run/todo.t

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -352,41 +352,6 @@ TODO: {
352352
is($?, 0, "No panic; GH 16971");
353353
}
354354

355-
TODO: {
356-
local $::TODO = 'GH 18669';
357-
358-
my $x = { arr => undef };
359-
eval {
360-
push(@{ $x->{ decide } ? $x->{ not_here } : $x->{ new } }, "mana");
361-
};
362-
unlike(
363-
$@,
364-
qr/Not an ARRAY reference/,
365-
"push on non-existent hash entry does not throw 'Not an ARRAY reference' error; GH 18669"
366-
);
367-
is(
368-
eval { $x->{ new }[0] },
369-
'mana',
370-
'push on non-existent hash entry from ternary autovivifies array ref; GH 18669'
371-
);
372-
373-
$x = { arr => undef };
374-
eval {
375-
push(@{ $x->{ decide } ? $x->{ not_here } : $x->{ arr } }, "mana");
376-
};
377-
unlike(
378-
$@,
379-
qr/Not an ARRAY reference/,
380-
"push on undef hash entry does not throw 'Not an ARRAY reference' error; GH 18669"
381-
);
382-
is(
383-
eval { $x->{ arr }[0] },
384-
'mana',
385-
'push on undef hash entry from ternary autovivifies array ref; GH 18669'
386-
);
387-
388-
}
389-
390355
TODO: {
391356
local $::TODO = 'GH 19378';
392357
fresh_perl_like(

0 commit comments

Comments
 (0)