From 23355e7dd54119404707535cd5505c9633d2df7a Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Thu, 22 Oct 2026 08:25:59 +0000 Subject: [PATCH] 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. --- 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 8e7611344b9150..00fc5102c63e3b 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2427,6 +2427,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 00000000000000..0e52137bde01ed --- /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 47c2c4abc3ef24..8d4b46c0fd14b0 100644 --- a/Modules/_sre/sre.c +++ b/Modules/_sre/sre.c @@ -899,6 +899,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)