From 5c80513d27aa6f101ed66dd7427dc19e7f886b6e Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" <68491+gpshead@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:14:26 -0700 Subject: [PATCH] gh-160316: Make `re.Pattern.match()` return `None` when `pos > endpos` (GH-160317) gh-160316: Make re.Pattern.match() return None when pos > endpos Pattern.match(), prefixmatch(), fullmatch() and the scanner match() ran the engine with the start position past the end position, which could return an empty match or read past the end of the string. ANT-2026-NMTUWIC8 (cherry picked from commit 6b58f9a6a12a3c514a755e5e86dc9b9f36bacb9d) Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> --- Lib/test/test_re.py | 19 +++++++++++++++++++ ...-10-22-08-17-51.gh-issue-160316.sRe9pQ.rst | 5 +++++ Modules/_sre/sre.c | 4 ++++ 3 files changed, 28 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-22-08-17-51.gh-issue-160316.sRe9pQ.rst diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 021a8a3fe29cd6f..13d48339c932ca7 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2194,6 +2194,25 @@ def test_keyword_parameters(self): pat.scanner(string='abracadabra', pos=3, endpos=10).search().span(), (7, 9)) + def test_pos_greater_than_endpos(self): + # If endpos is less than pos, no match will be found. + for pattern in ['', r'$', r'^$', r'\b$', r'(?:x|$)']: + for string in ['abc\n', b'abc\n', bytearray(b'abc\n')]: + with self.subTest(pattern=pattern, string=string): + if isinstance(string, str): + p = re.compile(pattern, re.MULTILINE) + else: + p = re.compile(pattern.encode(), re.MULTILINE) + for pos, endpos in [(3, 0), (4, 0), (4, 3), (2, 1)]: + self.assertIsNone(p.match(string, pos, endpos)) + self.assertIsNone(p.fullmatch(string, pos, endpos)) + self.assertIsNone(p.search(string, pos, endpos)) + self.assertIsNone( + p.scanner(string, pos, endpos).match()) + self.assertIsNone( + p.scanner(string, pos, endpos).search()) + self.assertEqual(p.findall(string, pos, endpos), []) + def test_bug_20998(self): # Issue #20998: Fullmatch of repeated single character pattern # with ignore case. diff --git a/Misc/NEWS.d/next/Library/2026-10-22-08-17-51.gh-issue-160316.sRe9pQ.rst b/Misc/NEWS.d/next/Library/2026-10-22-08-17-51.gh-issue-160316.sRe9pQ.rst new file mode 100644 index 000000000000000..0e52137bde01ed5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-22-08-17-51.gh-issue-160316.sRe9pQ.rst @@ -0,0 +1,5 @@ +Fix :meth:`re.Pattern.match`, :meth:`re.Pattern.prefixmatch`, +:meth:`re.Pattern.fullmatch` and the ``match()`` and ``prefixmatch()`` methods +of scanner objects returning an empty match, or reading past the end of the +string, when *pos* is greater than *endpos*. They now return ``None``, as +documented. diff --git a/Modules/_sre/sre.c b/Modules/_sre/sre.c index ace6b17ee2d93cf..2e1bbe7b0746c26 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -746,6 +746,10 @@ pattern_dealloc(PyObject *self) LOCAL(Py_ssize_t) sre_match(SRE_STATE* state, SRE_CODE* pattern) { + /* No match if pos > endpos. */ + if (state->ptr > state->end) { + return 0; + } if (state->charsize == 1) return sre_ucs1_match(state, pattern, 1); if (state->charsize == 2)