Commit d8f15d0ddf for perl
commit d8f15d0ddf5fae8f5b5605aa7ac04f9a7c27cd9f
Author: Richard Leach <rich+perl@hyphen-dash-hyphen.info>
Date: Sun Oct 11 22:05:34 2026 +0000
Perl_re_intuit_start: don't shrink strend for anchored MBOL matches
For a while, anchored matches have used the following to determine the
offset argument to pass to `HOP3clim` (or predecessors):
(prog->minlen ? cl_l : 0)
The return value is assigned to `endpos`, which is passed in the place
of `strend` (thought it could actually _be_ `strend`) to `S_find_byclass`.
Exactly what that logic is - or was - supposed to do has long been
unclear. Long-standing comments were opaque and fresher comments
stated as much, along with noting that nothing in the test harness
exercised the `prog->minlen` case.
GH #24944 provided an example in the form of a test case that should
match but failed to do so:
"a" =~ /\A^\ba*\z/m
Specifically:
* `MBOL` (`^`)`made that an anchored substring
* The pattern can successfully match with zero characters
* The ternary in question resulted in `endpos` matching `rx_origin`
* Within `S_find_byclass`, the `REXEC_FBC_NON_UTF8_SCAN` loop used
by the `BOUND` case was only only entered if `endpos` > `rx_origin`.
* Not entering that loop caused the match to fail.
This commit replaces the ternary with just `cl_l`, making the test case
pass and not breaking anything in the test harness.
Because of the historical uncertainty around the logic in the first
place, including no test coverage, it is unclear whether there could
be any assumption or code in the wild that depended on the old
behaviour.
(Note: Improvements to the original PR code comments and the revision
to the `DEBUG_EXECUTE_r` statement were provided by _demerphq_.)
diff --git a/pod/perldelta.pod b/pod/perldelta.pod
index afd3038dc2..7eb63289c9 100644
--- a/pod/perldelta.pod
+++ b/pod/perldelta.pod
@@ -497,6 +497,14 @@ contained a C<(*SKIP)> and was initially unsuccessful.
=item *
+A anchored regular expression needing zero (or more) characters for a
+successful match could have failed because the search space was
+incorrectly truncated.
+
+[GH #24944]
+
+=item *
+
In some cases warnings that occurred while using unpack on an invalid
UTF-8 marked string would result in an invalid free. [GH #24913]
diff --git a/regexec.c b/regexec.c
index eed547a922..565ca2da0e 100644
--- a/regexec.c
+++ b/regexec.c
@@ -1694,24 +1694,23 @@ Perl_re_intuit_start(pTHX_
* in that range. if neither, then look for the start class in the
* whole rest of the string */
- /* XXX DAPM it's not clear what the minlen test is for, and why
- * it's not used in the floating case. Nothing in the test suite
- * causes minlen == 0 here. See <20140313134639.GS12844@iabyn.com>.
- * Here are some old comments, which may or may not be correct:
+ /* Keep in mind that endpos limits the scan, not the match itself.
+ * Even when minlen == 0, a zero-width start class such as \b must
+ * examine the character at rx_origin. Using rx_origin as the
+ * limit would skip that check and treat this position as the end
+ * of the string (GH#24944).
*
- * minlen == 0 is possible if regstclass is \b or \B,
- * and the fixed substr is ''$.
- * Since minlen is already taken into account, rx_origin+1 is
- * before strend; accidentally, minlen >= 1 guaranties no false
- * positives at rx_origin + 1 even for \b or \B. But (minlen? 1 :
- * 0) below assumes that regstclass does not come from lookahead...
- * If regstclass takes bytelength more than 1: If charlength == 1, OK.
- * This leaves EXACTF-ish only, which are dealt with in
- * find_byclass().
+ * Allow the scan to examine cl_l characters, clamped to the real
+ * string end, so empty matches there are preserved.
+ *
+ * \b or other ZW assertions may report a false positive at this
+ * artificial endpoint but that is harmless here as a successful
+ * scan leaves rx_origin unchanged, and the full matcher checks
+ * the pattern a second time before returning success.
*/
if (prog->anchored_substr || prog->anchored_utf8 || ml_anch)
- endpos = HOP3clim(rx_origin, (prog->minlen ? cl_l : 0), strend);
+ endpos = HOP3clim(rx_origin, cl_l, strend);
else if (prog->float_substr || prog->float_utf8) {
rx_max_float = HOP3c(check_at, -start_shift, strbeg);
endpos = HOP3clim(rx_max_float, cl_l, strend);
@@ -1816,8 +1815,9 @@ Perl_re_intuit_start(pTHX_
if (rx_origin != s) {
DEBUG_EXECUTE_r(re_printf(
- " By STCLASS: moving %ld --> %ld\n",
- (long)(rx_origin - strbeg), (long)(s - strbeg))
+ " STCLASS scan returned offset %ld"
+ " (rx_origin kept at %ld)\n",
+ (long)(s - strbeg), (long)(rx_origin - strbeg))
);
}
else {
diff --git a/t/re/re_tests b/t/re/re_tests
index d3407ef0c9..2be484c8cf 100644
--- a/t/re/re_tests
+++ b/t/re/re_tests
@@ -2367,6 +2367,11 @@ a+X(*COMMIT)j aXaXj n - - - PLUS skip after (*COMMIT) [ASCII]
(?{})\A(?:(\x{100})?b)+\z \x{100}bb y ${\(defined($1) ? $1 : q(undef))} undef - Unicode optional capture
(?{})\A(?:(a)?b)+\z abab y $1 a - final participating capture is preserved
+# GH #24944
+/\A\ba*\z/m a y $& a - SBOL and BOUND start class should match
+/^\ba*\z/m a y $& a - MBOL and BOUND start class should match
+/\A^\ba*\z/m a y $& a - MBOL and SBOL and BOUND start class should match
+
# Keep these lines at the end of the file
# pat string y/n/etc expr expected-expr skip-reason comment
# vim: softtabstop=0 noexpandtab