Skip to content

Conversation

@iabyn
Copy link
Contributor

@iabyn iabyn commented Nov 11, 2025

Fix GH #18669 and tidy up the function where the bug was found, Perl_doref()

  • This set of changes requires a perldelta entry, and i'll get rouhnd to writing it at some point

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.
This compile-time function propagates lvalue ref context down a chain of
ops. It does the same thing (setting OPf_MOD and OPpDEREF_XV flags) in
three places. Consolidate this code into a single place.

Should be no functional changes.

Technically the code is slightly different in that OP_[AH]ELEM now
checks for kids before following them, but since they always have kids,
this makes no difference (except being infinitesimally slower during
compilation).
Having just messed with this function, I understand it better, so can
comment it better.
@jkeenan
Copy link
Contributor

jkeenan commented Nov 11, 2025

Tests look good, but C code should be reviewed by someone other than me.

@iabyn iabyn merged commit b586026 into blead Nov 12, 2025
68 checks passed
@iabyn iabyn deleted the davem/condref_fix branch November 12, 2025 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants