Skip to content

newFOROP: fix crash when optimizing 2-var for over builtin::indexed #23429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions op.c
Original file line number Diff line number Diff line change
Expand Up @@ -9652,6 +9652,33 @@ Perl_newWHILEOP(pTHX_ I32 flags, I32 debuggable, LOOP *loop,
return o;
}

#define find_argop_from_entersub(op) S_find_argop_from_entersub(op)
static OP *
S_find_argop_from_entersub(OP *entersubop) {
assert(entersubop != NULL);

OP *aop = cUNOPx(entersubop)->op_first;
if (!OpHAS_SIBLING(aop)) {
aop = cUNOPx(aop)->op_first;
}
/* move past pushmark */
aop = OpSIBLING(aop);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be tempted to add an assert for this, just to check

assert(OpTYPE(aop) == OP_PUSHMARK);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of the other parts I cribbed this code from had an assert there and I didn't want to make the code "crashier" than before.


return aop;
}

#define find_cvop_from_argop(op) S_find_cvop_from_argop(op)
static OP *
S_find_cvop_from_argop(OP *cvop) {
assert(cvop != NULL);

/* CV is the last argument to entersub */
while (OpHAS_SIBLING(cvop)) {
cvop = OpSIBLING(cvop);
}
return cvop;
}

#define op_is_cv_xsub(o, xsub) S_op_is_cv_xsub(aTHX_ o, xsub)
static bool
S_op_is_cv_xsub(pTHX_ OP *o, XSUBADDR_t xsub)
Expand All @@ -9671,7 +9698,7 @@ S_op_is_cv_xsub(pTHX_ OP *o, XSUBADDR_t xsub)
}

case OP_PADCV:
cv = (CV *)PAD_SVl(o->op_targ);
cv = find_lexical_cv(o->op_targ);
assert(cv && SvTYPE(cv) == SVt_PVCV);
break;

Expand All @@ -9689,10 +9716,13 @@ S_op_is_cv_xsub(pTHX_ OP *o, XSUBADDR_t xsub)
static bool
S_op_is_call_to_cv_xsub(pTHX_ OP *o, XSUBADDR_t xsub)
{
if(o->op_type != OP_ENTERSUB)
if (o->op_type != OP_ENTERSUB)
return false;

OP *cvop = cLISTOPx(cUNOPo->op_first)->op_last;
/* entersub may be a UNOP, not a LISTOP, so we can't just use op_last */
OP *aop = find_argop_from_entersub(o);
OP *cvop = find_cvop_from_argop(aop);

return op_is_cv_xsub(cvop, xsub);
}

Expand Down Expand Up @@ -14715,10 +14745,7 @@ Perl_ck_entersub_args_list(pTHX_ OP *entersubop)

PERL_ARGS_ASSERT_CK_ENTERSUB_ARGS_LIST;

aop = cUNOPx(entersubop)->op_first;
if (!OpHAS_SIBLING(aop))
aop = cUNOPx(aop)->op_first;
for (aop = OpSIBLING(aop); OpHAS_SIBLING(aop); aop = OpSIBLING(aop)) {
for (aop = find_argop_from_entersub(entersubop); OpHAS_SIBLING(aop); aop = OpSIBLING(aop)) {
/* skip the extra attributes->import() call implicitly added in
* something like foo(my $x : bar)
*/
Expand Down Expand Up @@ -14765,7 +14792,7 @@ Perl_ck_entersub_args_proto(pTHX_ OP *entersubop, GV *namegv, SV *protosv)
{
STRLEN proto_len;
const char *proto, *proto_end;
OP *aop, *prev, *cvop, *parent;
OP *aop, *prev, *parent;
int optional = 0;
I32 arg = 0;
I32 contextclass = 0;
Expand All @@ -14787,7 +14814,7 @@ Perl_ck_entersub_args_proto(pTHX_ OP *entersubop, GV *namegv, SV *protosv)
}
prev = aop;
aop = OpSIBLING(aop);
for (cvop = aop; OpHAS_SIBLING(cvop); cvop = OpSIBLING(cvop)) ;
OP *cvop = find_cvop_from_argop(aop);
while (aop != cvop) {
OP* o3 = aop;

Expand Down Expand Up @@ -15022,18 +15049,17 @@ Perl_ck_entersub_args_proto_or_list(pTHX_ OP *entersubop,
OP *
Perl_ck_entersub_args_core(pTHX_ OP *entersubop, GV *namegv, SV *protosv)
{
PERL_ARGS_ASSERT_CK_ENTERSUB_ARGS_CORE;

IV cvflags = SvIVX(protosv);
int opnum = cvflags & 0xffff;
OP *aop = cUNOPx(entersubop)->op_first;

PERL_ARGS_ASSERT_CK_ENTERSUB_ARGS_CORE;

if (!opnum) {
OP *cvop;
if (!OpHAS_SIBLING(aop))
aop = cUNOPx(aop)->op_first;
aop = OpSIBLING(aop);
for (cvop = aop; OpSIBLING(cvop); cvop = OpSIBLING(cvop)) ;
OP *cvop = find_cvop_from_argop(aop);
if (aop != cvop) {
SV *namesv = cv_name((CV *)namegv, NULL, CV_NAME_NOTQUAL);
yyerror_pv(form("Too many arguments for %" SVf,
Expand Down Expand Up @@ -15311,21 +15337,15 @@ S_entersub_alloc_targ(pTHX_ OP * const o)
OP *
Perl_ck_subr(pTHX_ OP *o)
{
OP *aop, *cvop;
CV *cv;
GV *namegv;
SV **const_class = NULL;
OP *const_op = NULL;

PERL_ARGS_ASSERT_CK_SUBR;

aop = cUNOPx(o)->op_first;
if (!OpHAS_SIBLING(aop))
aop = cUNOPx(aop)->op_first;
aop = OpSIBLING(aop);
for (cvop = aop; OpHAS_SIBLING(cvop); cvop = OpSIBLING(cvop)) ;
cv = rv2cv_op_cv(cvop, RV2CVOPCV_MARK_EARLY);
namegv = cv ? (GV*)rv2cv_op_cv(cvop, RV2CVOPCV_MAYBE_NAME_GV) : NULL;
OP *aop = find_argop_from_entersub(o);
OP *cvop = find_cvop_from_argop(aop);
CV *cv = rv2cv_op_cv(cvop, RV2CVOPCV_MARK_EARLY);
GV *namegv = cv ? (GV*)rv2cv_op_cv(cvop, RV2CVOPCV_MAYBE_NAME_GV) : NULL;

o->op_private &= ~1;
o->op_private |= (PL_hints & HINT_STRICT_REFS);
Expand Down
24 changes: 23 additions & 1 deletion pod/perldelta.pod
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,29 @@ manager will later use a regex to expand these into links.

=item *

XXX
Certain constructs involving a two-variable C<for> loop would crash the perl
compiler in v5.42.0:

# Two-variable for loop over a list returned from a method call:
for my ($x, $y) (Some::Class->foo()) { ... }
for my ($x, $y) ($object->foo()) { ... }

and

# Two-variable for loop over a list returned from a call to a
# lexical(ly imported) subroutine, all inside a lexically scoped
# or anonymous subroutine:
my sub foo { ... }
my $fn = sub {
for my ($x, $y) (foo()) { ... }
};

use builtin qw(indexed); # lexical import!
my sub bar {
for my ($x, $y) (indexed(...)) { ... }
}

These have been fixed. [GH #23405]

=back

Expand Down
13 changes: 13 additions & 0 deletions t/op/for-many.t
Original file line number Diff line number Diff line change
Expand Up @@ -498,4 +498,17 @@ is($continue, 'xx', 'continue reached twice');
is("@have", "Pointy end Up Flamey end Down", 'for my ($one, $two)');
}

# GH #23405 - segfaults when compiling 2-var for loops
{
my $dummy = sub {};
for my ($x, $y) (main->$dummy) {}
pass '2-var for does not crash on method calls';

my sub dummy {}
sub {
for my ($x, $y) (dummy) {}
}->();
pass '2-var for does not crash on lexical sub calls';
}

done_testing();
Loading
Loading