Commit 9e049654e2 for perl

commit 9e049654e20db808db6a1c4eeec53e7b21ae0d39
Author: Yves Orton <demerphq@gmail.com>
Date:   Mon Oct 12 23:44:56 2026 +0200

    regcomp.c: preserve captures when nested repeat iterations fail

    We leave repeats containing captures inside nested quantifiers as
    CURLYX. CURLYM can reconstruct a capture around its whole body, but
    cannot restore captures inside a nested quantifier when an iteration
    fails or is backtracked. CURLYX already has the iteration checkpoints
    needed to restore these captures.

    For example, "aa" =~ /(?:(aa){2})*/ matches the empty string and now
    leaves $1 undefined. Likewise, "aabbaa" =~ /(?:([ab]{2}){2})*/ matches
    "aabb" and preserves "bb" from the completed iteration, rather than
    "aa" from the failed next iteration.

    Fixes:
        https://github.com/Perl/perl5/issues/24945

diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index 08f2ccceca..c3c3ed2186 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -434,6 +434,16 @@ C</\A(?=a*)[0-9]a\z/> now matches C<"0a">.

 =item *

+The regular expression optimizer no longer uses the fixed-width repeat
+optimization for a body containing captures inside nested quantifiers.
+This prevents failed or backtracked iterations from leaving incorrect
+capture values. For example, C<"aa" =~ /(?:(aa){2})*/> now leaves C<$1>
+undefined.
+
+[GH #24945]
+
+=item *
+
 Line number information is stored rather than discarded when an anonymous
 subroutine is encountered during compilation. This should improve the
 accuracy of line numbers emitted by diagnostic messages and `caller()`
diff --git a/regcomp_study.c b/regcomp_study.c
index 5611f0615d..12958d5ca0 100644
--- a/regcomp_study.c
+++ b/regcomp_study.c
@@ -2704,6 +2704,14 @@ Perl_study_chunk(pTHX_
                                                             non optimistic eval is seen
                                                             anywhere.*/
                      && !(data->flags & SF_HAS_PAR) /* no parens! */
+                     /* CURLYM can emulate a capture around its whole body,
+                      * but cannot restore captures inside a nested quantifier
+                      * when an iteration fails or is backtracked. */
+                     && (!ARG3a(oscan)
+                         || ((data->flags & SF_IN_PAR)
+                             && ARG3a(oscan) == ARG3b(oscan)
+                             && OP(REGNODE_AFTER_type(oscan,
+                                                tregnode_CURLYX)) == OPEN))
                      && !deltanext     /* atom is fixed width */
                      && minnext != 0  /* CURLYM can't handle zero width */
                          /* Nor characters whose fold at run-time may be
diff --git a/t/re/re_tests b/t/re/re_tests
index db50922101..b13f3112ef 100644
--- a/t/re/re_tests
+++ b/t/re/re_tests
@@ -2313,6 +2313,20 @@ a+X(*COMMIT)j	aXaXj	n	-	-	-	PLUS skip after (*COMMIT) [ASCII]
 \A(?=(?=a)b*)[a-z]a\z	aa	y	$&	aa	-	zero-length body with a required nested assertion
 \A(?=(?=a)b*)[a-z]a\z	ba	n	-	-	-	required nested assertion must still be checked

+# GH #24945: captures from failed iterations of nested fixed-width quantifiers
+(?:(aa){2})*	aa	y	${\(defined($1) ? $1 : q(undef))}	undef	-	failed first outer iteration must not leave a capture
+(?:([ab]{2}){2})*	aabbaa	y	$&-$1	aabb-bb	-	failed next outer iteration must restore the last completed capture
+\A(?:([a-d]{2}){2})*ccdd\z	aabbccdd	y	$1	bb	-	backtracking an outer iteration must restore the capture
+\A(?:([a-d]{2}){2})*?ccdd\z	aabbccdd	y	$1	bb	-	lazy outer quantifier with a nested capture
+\A(?:([a-d]{2}){2}){0,2}ccdd\z	aabbccdd	y	$1	bb	-	bounded outer quantifier must restore the capture
+\A(?:(aa){2})*aa\z	aa	y	${\(defined($1) ? $1 : q(undef))}	undef	-	tail succeeds after a failed first iteration
+\A(?:([ab]){4})*aa\z	aabbaa	y	$1	b	-	nested CURLYN capture must be restored
+\A(?:([ab]{2}){2})*\1\z	aabbbb	y	$1	bb	-	backreference uses the last completed iteration
+\A((?:([a-d]{2}){2})*)ccdd\z	aabbccdd	y	$1-$2	aabb-bb	-	outer capture around a nested quantifier
+\A(?:((aa){2}))*aa\z	aa	y	${\(defined($1) || defined($2) ? q(set) : q(undef))}	undef	-	multiple captures in a failed first iteration
+(?:(\x{100}\x{101}){2})*	\x{100}\x{101}	y	${\(defined($1) ? $1 : q(undef))}	undef	-	failed UTF-8 iteration must not leave a capture
+\A(?:([\x{100}-\x{103}]{2}){2})*\x{102}\x{102}\x{103}\x{103}\z	\x{100}\x{100}\x{101}\x{101}\x{102}\x{102}\x{103}\x{103}	y	$1	\x{101}\x{101}	-	UTF-8 backtracking restores the capture
+
 # Keep these lines at the end of the file
 # pat	string	y/n/etc	expr	expected-expr	skip-reason	comment
 # vim: softtabstop=0 noexpandtab