Commit 2d36917781 for perl

commit 2d3691778163f15eb7aed772adbbd8fd2848874c
Author: Yves Orton <demerphq@gmail.com>
Date:   Mon Oct 12 22:32:55 2026 +0200

    regcomp: do not restrict the start class with a 0 length lookahead-assertion

    For /\A(?=a*)[0-9]a\z/, study_chunk() derives a class containing 'a'
    and the empty string from the STAR inside the IFMATCH. Intersecting
    that class with [0-9] produces ANYOF[], so the optimizer rejects "0a"
    before we even try the pattern in the regex engine proper.

    With this change the resulting start class is ANYOF[0-9], and the pattern
    matches.

    Lookahead bodies with a positive minimum length continue to contribute
    their start-class constraints. We apply the same rule to the optional
    positive assertion study path.

    Adds new tests which fail without the fix.

    I wonder if we could be a bit more clear in the debug output that
    ANYOF[] means "pattern can never match".

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

diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index 7d3d7a4baf..08f2ccceca 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -426,6 +426,14 @@ manager will later use a regex to expand these into links.

 =item *

+The regular expression optimizer no longer rejects valid matches when a
+positive lookahead can match an empty string. For example,
+C</\A(?=a*)[0-9]a\z/> now matches C<"0a">.
+
+[GH #24943]
+
+=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 29b229b857..5611f0615d 100644
--- a/regcomp_study.c
+++ b/regcomp_study.c
@@ -3258,6 +3258,12 @@ Perl_study_chunk(pTHX_
                                       recursed_depth, NULL, f, depth+1,
                                       mutate_ok);

+                /* A lookahead body which can match without consuming any
+                 * characters cannot restrict the next input character to
+                 * its consuming paths.  Treat its start class as anything. */
+                if ((f & SCF_DO_STCLASS_AND) && minnext == 0)
+                    ssc_init(pRExC_state, &intrnl);
+
                 if (FLAGS(scan)) {
                     if (   deltanext < 0
                         || deltanext > (I32) U8_MAX
@@ -3376,6 +3382,8 @@ Perl_study_chunk(pTHX_
                                         &deltanext, last, &data_fake,
                                         stopparen, recursed_depth, NULL,
                                         f, depth+1, mutate_ok);
+                if ((f & SCF_DO_STCLASS_AND) && *minnextp == 0)
+                    ssc_init(pRExC_state, &intrnl);
                 if (FLAGS(scan)) {
                     assert(0);  /* This code has never been tested since this
                                    is normally not compiled */
diff --git a/t/re/opt.t b/t/re/opt.t
index ad7323e3fe..f184e15b91 100644
--- a/t/re/opt.t
+++ b/t/re/opt.t
@@ -292,3 +292,5 @@ abc(*ACCEPT)xyz	3	0+abc	-	-
 \R	1	-	-	stclass=LNBREAK	\R start class
 \Rfoo	4	-	1:2+foo	stclass=LNBREAK	\R start class with floating substr
 \R+foo	4	-	1+foo	skip,stclass=LNBREAK	\R+ start class with PLUS and floating substr
+
+\A(?=a*)[0-9]a	2	1+a	-	noscan,anchor SBOL,stclass=~^ANYOF\[0-9\]	nullable lookahead does not restrict the start class
diff --git a/t/re/re_tests b/t/re/re_tests
index a23ae33cae..db50922101 100644
--- a/t/re/re_tests
+++ b/t/re/re_tests
@@ -2295,6 +2295,24 @@ a+X(*COMMIT)j	aXaXj	n	-	-	-	PLUS skip after (*COMMIT) [ASCII]
 (?:x+?\x{100})*a+	aaaa	y	$&	aaaa	-	minmod cleared by PLUS
 (?:x*?\x{100})*a+	aaaa	y	$&	aaaa	-	minmod cleared by STAR

+# GH #24943
+\A(?=a*)[0-9]a\z	0a	y	$&	0a	-	nullable lookahead must not restrict the start class
+\A(?=a*?)[0-9]a\z	0a	y	$&	0a	-	lazy nullable lookahead
+\A(?=a?)[0-9]a\z	0a	y	$&	0a	-	optional lookahead body
+\A(?=a{0,3})[0-9]a\z	0a	y	$&	0a	-	bounded nullable lookahead
+\A(?=(?:ab)*)[0-9]a\z	0a	y	$&	0a	-	nullable multi-character lookahead
+\A(?=(?:a|))[0-9]a\z	0a	y	$&	0a	-	lookahead with an empty alternative
+\A(?=(?=a*)b*)[0-9]a\z	0a	y	$&	0a	-	nested nullable lookahead
+(?=a*)[0-9]a	zz0a	y	$&	0a	-	unanchored nullable lookahead
+\A(?=a*)[0-9]a\z	ba	n	-	-	-	consuming class must still be checked
+\A(?=a+)[0-9]a\z	0a	n	-	-	-	nonnullable lookahead must still be checked
+\A(?=a*b)[0-9]a\z	0a	n	-	-	-	nonnullable suffix in lookahead
+\A(?=a*)[a-z]a\z	ba	y	$&	ba	-	consuming class partly overlaps the nullable body
+\A(?=\x{100}*)[0-9]a\z	0a	y	$&	0a	-	nullable Unicode lookahead
+\A(?=a*)\x{100}a\z	\x{100}a	y	$&	\x{100}a	-	Unicode consuming node after nullable lookahead
+\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
+
 # Keep these lines at the end of the file
 # pat	string	y/n/etc	expr	expected-expr	skip-reason	comment
 # vim: softtabstop=0 noexpandtab