Commit 62c11b003c for perl

commit 62c11b003c49ce2d835edf2bebca9f255272884d
Author: Yves Orton <demerphq@gmail.com>
Date:   Sat Oct 10 21:14:11 2026 +0200

    regex: stop folded start-class scans at end of UTF-8 input

    A nonempty folded literal needs an input character. HOP3c can leave the
    last possible start at the end of a short target, where UTF8_SAFE_SKIP()
    returns zero. We then retry the same failed match forever.

    Check that input remains before trying the literal. This can happen on
    the next /g attempt when prefix extraction leaves a folded start class.
    The same problem occurs on blead without the AC prefilter changes.

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

diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index 868225c5d9..745ff34ec4 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -427,7 +427,11 @@ manager will later use a regex to expand these into links.

 =item *

-XXX
+A case-insensitive regular expression with a folded literal start class
+could loop indefinitely when searching at the end of a UTF-8 string.
+This could occur on a subsequent C</g> match after a successful match.
+
+[GH #24934]

 =item *

diff --git a/regexec.c b/regexec.c
index 8587c94559..81307fa6ac 100644
--- a/regexec.c
+++ b/regexec.c
@@ -2894,7 +2894,10 @@ S_find_byclass(pTHX_ regexp * prog, const regnode *c, char *s,
              * where if there were no expansion we should fail.  Unclear if
              * worth the expense */

-            while (s <= e) {
+            /* HOP3c clamps e to s when too few characters remain.  A
+             * nonempty folded literal cannot match at strend, where
+             * UTF8_SAFE_SKIP returns zero and cannot advance the loop. */
+            while (s < strend && s <= e) {
                 char *my_strend= (char *)strend;
                 if (   foldEQ_utf8_flags(s, &my_strend, 0,  utf8_target,
                                          pat_string, NULL, ln, is_utf8_pat,
diff --git a/t/re/pat.t b/t/re/pat.t
index 7dce0a05d2..a4f353e070 100644
--- a/t/re/pat.t
+++ b/t/re/pat.t
@@ -28,7 +28,7 @@ skip_all_without_unicode_tables();
 my $has_locales = locales_enabled('LC_CTYPE');
 my $utf8_locale = find_utf8_ctype_locale();

-plan tests => 1780;  # Update this when adding/deleting tests.
+plan tests => 1786;  # Update this when adding/deleting tests.

 run_tests() unless caller;

@@ -96,6 +96,33 @@ sub run_tests {
         }
     }

+    {
+        for my $trie_limit (65536, -1) {
+            local ${^RE_TRIE_MAXBUF} = $trie_limit;
+            my $pattern = "\\x{df}\\x{10000}\\x{100}|\\x{df}\\x{e9}a"
+                        . "(?#trie limit $trie_limit)";
+            my $re = qr/$pattern/i;
+            my $target = ("\x{df}" x 3) . "\x{10000}\x{100}";
+            my @matches;
+
+            while ($target =~ /$re/g) {
+                push @matches, "$-[0]:$+[0]";
+            }
+
+            is(join(',', @matches), '2:5',
+               "folded global match terminates at UTF-8 end, trie limit $trie_limit");
+
+            pos($target) = length($target);
+            ok($target !~ /$re/g,
+               "folded start class rejects UTF-8 end, trie limit $trie_limit");
+
+            my $empty = '';
+            utf8::upgrade($empty);
+            ok($empty !~ /$re/,
+               "folded start class rejects empty UTF-8 target, trie limit $trie_limit");
+        }
+    }
+
     {
         my $haystack = ("A" x 5000) . "cat";
         ok($haystack =~ /walrus|cat/, 'AC finds a literal after a long rejected run');