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');