Skip to content

Commit c7ac728

Browse files
bpo-30375: Correct the stacklevel of regex compiling warnings. (#1595)
Warnings emitted when compile a regular expression now always point to the line in the user code. Previously they could point into inners of the re module if emitted from inside of groups or conditionals.
1 parent 87fa8a7 commit c7ac728

File tree

3 files changed

+29
-13
lines changed

3 files changed

+29
-13
lines changed

Lib/sre_parse.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -414,15 +414,16 @@ def _uniq(items):
414414
newitems.append(item)
415415
return newitems
416416

417-
def _parse_sub(source, state, verbose, nested=True):
417+
def _parse_sub(source, state, verbose, nested):
418418
# parse an alternation: a|b|c
419419

420420
items = []
421421
itemsappend = items.append
422422
sourcematch = source.match
423423
start = source.tell()
424424
while True:
425-
itemsappend(_parse(source, state, verbose, not nested and not items))
425+
itemsappend(_parse(source, state, verbose, nested + 1,
426+
not nested and not items))
426427
if not sourcematch("|"):
427428
break
428429

@@ -471,7 +472,7 @@ def _parse_sub(source, state, verbose, nested=True):
471472
subpattern.append((BRANCH, (None, items)))
472473
return subpattern
473474

474-
def _parse(source, state, verbose, first=False):
475+
def _parse(source, state, verbose, nested, first=False):
475476
# parse a simple pattern
476477
subpattern = SubPattern(state)
477478

@@ -708,7 +709,7 @@ def _parse(source, state, verbose, first=False):
708709
lookbehindgroups = state.lookbehindgroups
709710
if lookbehindgroups is None:
710711
state.lookbehindgroups = state.groups
711-
p = _parse_sub(source, state, verbose)
712+
p = _parse_sub(source, state, verbose, nested + 1)
712713
if dir < 0:
713714
if lookbehindgroups is None:
714715
state.lookbehindgroups = None
@@ -744,9 +745,9 @@ def _parse(source, state, verbose, first=False):
744745
msg = "invalid group reference %d" % condgroup
745746
raise source.error(msg, len(condname) + 1)
746747
state.checklookbehindgroup(condgroup, source)
747-
item_yes = _parse(source, state, verbose)
748+
item_yes = _parse(source, state, verbose, nested + 1)
748749
if source.match("|"):
749-
item_no = _parse(source, state, verbose)
750+
item_no = _parse(source, state, verbose, nested + 1)
750751
if source.next == "|":
751752
raise source.error("conditional backref with more than two branches")
752753
else:
@@ -768,7 +769,7 @@ def _parse(source, state, verbose, first=False):
768769
source.string[:20], # truncate long regexes
769770
' (truncated)' if len(source.string) > 20 else '',
770771
),
771-
DeprecationWarning, stacklevel=7
772+
DeprecationWarning, stacklevel=nested + 6
772773
)
773774
if (state.flags & SRE_FLAG_VERBOSE) and not verbose:
774775
raise Verbose
@@ -788,7 +789,7 @@ def _parse(source, state, verbose, first=False):
788789
raise source.error(err.msg, len(name) + 1) from None
789790
sub_verbose = ((verbose or (add_flags & SRE_FLAG_VERBOSE)) and
790791
not (del_flags & SRE_FLAG_VERBOSE))
791-
p = _parse_sub(source, state, sub_verbose)
792+
p = _parse_sub(source, state, sub_verbose, nested + 1)
792793
if not source.match(")"):
793794
raise source.error("missing ), unterminated subpattern",
794795
source.tell() - start)
@@ -886,15 +887,15 @@ def parse(str, flags=0, pattern=None):
886887
pattern.str = str
887888

888889
try:
889-
p = _parse_sub(source, pattern, flags & SRE_FLAG_VERBOSE, False)
890+
p = _parse_sub(source, pattern, flags & SRE_FLAG_VERBOSE, 0)
890891
except Verbose:
891892
# the VERBOSE flag was switched on inside the pattern. to be
892893
# on the safe side, we'll parse the whole thing again...
893894
pattern = Pattern()
894895
pattern.flags = flags | SRE_FLAG_VERBOSE
895896
pattern.str = str
896897
source.seek(0)
897-
p = _parse_sub(source, pattern, True, False)
898+
p = _parse_sub(source, pattern, True, 0)
898899

899900
p.pattern.flags = fix_flags(str, p.pattern.flags)
900901

Lib/test/test_re.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,7 @@ def test_inline_flags(self):
13701370
str(warns.warnings[0].message),
13711371
'Flags not at the start of the expression %s' % p
13721372
)
1373+
self.assertEqual(warns.warnings[0].filename, __file__)
13731374

13741375
p = upper_char + '(?i)%s' % ('.?' * 100)
13751376
with self.assertWarns(DeprecationWarning) as warns:
@@ -1378,6 +1379,7 @@ def test_inline_flags(self):
13781379
str(warns.warnings[0].message),
13791380
'Flags not at the start of the expression %s (truncated)' % p[:20]
13801381
)
1382+
self.assertEqual(warns.warnings[0].filename, __file__)
13811383

13821384
with self.assertWarns(DeprecationWarning):
13831385
self.assertTrue(re.match('(?s).(?i)' + upper_char, '\n' + lower_char))
@@ -1389,14 +1391,23 @@ def test_inline_flags(self):
13891391
self.assertTrue(re.match('^(?i)' + upper_char, lower_char))
13901392
with self.assertWarns(DeprecationWarning):
13911393
self.assertTrue(re.match('$|(?i)' + upper_char, lower_char))
1392-
with self.assertWarns(DeprecationWarning):
1394+
with self.assertWarns(DeprecationWarning) as warns:
13931395
self.assertTrue(re.match('(?:(?i)' + upper_char + ')', lower_char))
1394-
with self.assertWarns(DeprecationWarning):
1396+
self.assertRegex(str(warns.warnings[0].message),
1397+
'Flags not at the start')
1398+
self.assertEqual(warns.warnings[0].filename, __file__)
1399+
with self.assertWarns(DeprecationWarning) as warns:
13951400
self.assertTrue(re.fullmatch('(^)?(?(1)(?i)' + upper_char + ')',
13961401
lower_char))
1397-
with self.assertWarns(DeprecationWarning):
1402+
self.assertRegex(str(warns.warnings[0].message),
1403+
'Flags not at the start')
1404+
self.assertEqual(warns.warnings[0].filename, __file__)
1405+
with self.assertWarns(DeprecationWarning) as warns:
13981406
self.assertTrue(re.fullmatch('($)?(?(1)|(?i)' + upper_char + ')',
13991407
lower_char))
1408+
self.assertRegex(str(warns.warnings[0].message),
1409+
'Flags not at the start')
1410+
self.assertEqual(warns.warnings[0].filename, __file__)
14001411

14011412

14021413
def test_dollar_matches_twice(self):

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,10 @@ Extension Modules
323323
Library
324324
-------
325325

326+
- bpo-30375: Warnings emitted when compile a regular expression now always
327+
point to the line in the user code. Previously they could point into inners
328+
of the re module if emitted from inside of groups or conditionals.
329+
326330
- bpo-30329: imaplib and poplib now catch the Windows socket WSAEINVAL error
327331
(code 10022) on shutdown(SHUT_RDWR): An invalid operation was attempted.
328332
This error occurs sometimes on SSL connections.

0 commit comments

Comments
 (0)