Skip to content

Commit 2c9cf64

Browse files
[3.12] gh-112343: pdb: Use tokenize to replace convenience variables (GH-112380) (#114202)
1 parent c1890e6 commit 2c9cf64

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

Lib/pdb.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
import dis
7777
import code
7878
import glob
79+
import token
7980
import pprint
8081
import signal
8182
import inspect
@@ -467,6 +468,39 @@ def default(self, line):
467468
except:
468469
self._error_exc()
469470

471+
def _replace_convenience_variables(self, line):
472+
"""Replace the convenience variables in 'line' with their values.
473+
e.g. $foo is replaced by __pdb_convenience_variables["foo"].
474+
Note: such pattern in string literals will be skipped"""
475+
476+
if "$" not in line:
477+
return line
478+
479+
dollar_start = dollar_end = -1
480+
replace_variables = []
481+
try:
482+
for t in tokenize.generate_tokens(io.StringIO(line).readline):
483+
token_type, token_string, start, end, _ = t
484+
if token_type == token.OP and token_string == '$':
485+
dollar_start, dollar_end = start, end
486+
elif start == dollar_end and token_type == token.NAME:
487+
# line is a one-line command so we only care about column
488+
replace_variables.append((dollar_start[1], end[1], token_string))
489+
except tokenize.TokenError:
490+
return line
491+
492+
if not replace_variables:
493+
return line
494+
495+
last_end = 0
496+
line_pieces = []
497+
for start, end, name in replace_variables:
498+
line_pieces.append(line[last_end:start] + f'__pdb_convenience_variables["{name}"]')
499+
last_end = end
500+
line_pieces.append(line[last_end:])
501+
502+
return ''.join(line_pieces)
503+
470504
def precmd(self, line):
471505
"""Handle alias expansion and ';;' separator."""
472506
if not line.strip():
@@ -492,7 +526,8 @@ def precmd(self, line):
492526
line = line[:marker].rstrip()
493527

494528
# Replace all the convenience variables
495-
line = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'__pdb_convenience_variables["\1"]', line)
529+
line = self._replace_convenience_variables(line)
530+
496531
return line
497532

498533
def onecmd(self, line):

Lib/test/test_pdb.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions():
389389
1 breakpoint keep yes at ...test_pdb.py:...
390390
2 breakpoint keep yes at ...test_pdb.py:...
391391
(Pdb) break pdb.find_function
392-
Breakpoint 3 at ...pdb.py:97
392+
Breakpoint 3 at ...pdb.py:...
393393
(Pdb) break
394394
Num Type Disp Enb Where
395395
1 breakpoint keep yes at ...test_pdb.py:...
@@ -770,9 +770,12 @@ def test_convenience_variables():
770770
771771
>>> with PdbTestInput([ # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
772772
... '$_frame.f_lineno', # Check frame convenience variable
773+
... '$ _frame', # This should be a syntax error
773774
... '$a = 10', # Set a convenience variable
774775
... '$a', # Print its value
776+
... 'p "$a"', # Print the string $a
775777
... 'p $a + 2', # Do some calculation
778+
... 'p f"$a = {$a}"', # Make sure $ in string is not converted and f-string works
776779
... 'u', # Switch frame
777780
... '$_frame.f_lineno', # Make sure the frame changed
778781
... '$a', # Make sure the value persists
@@ -792,11 +795,17 @@ def test_convenience_variables():
792795
-> try:
793796
(Pdb) $_frame.f_lineno
794797
3
798+
(Pdb) $ _frame
799+
*** SyntaxError: invalid syntax
795800
(Pdb) $a = 10
796801
(Pdb) $a
797802
10
803+
(Pdb) p "$a"
804+
'$a'
798805
(Pdb) p $a + 2
799806
12
807+
(Pdb) p f"$a = {$a}"
808+
'$a = 10'
800809
(Pdb) u
801810
> <doctest test.test_pdb.test_convenience_variables[1]>(2)test_function()
802811
-> util_function()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve handling of pdb convenience variables to avoid replacing string contents.

0 commit comments

Comments
 (0)