Skip to content

Commit f9c0809

Browse files
authored
Merge pull request #537 from python/main
Sync Fork from Upstream Repo
2 parents 0afde35 + 2c20558 commit f9c0809

File tree

11 files changed

+249
-84
lines changed

11 files changed

+249
-84
lines changed

Doc/distributing/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,11 @@ involved in creating and publishing a project:
131131
* `The .pypirc file`_
132132

133133
.. _Project structure: \
134-
https://packaging.python.org/tutorials/distributing-packages/
134+
https://packaging.python.org/tutorials/packaging-projects/#packaging-python-projects
135135
.. _Building and packaging the project: \
136-
https://packaging.python.org/tutorials/distributing-packages/#packaging-your-project
136+
https://packaging.python.org/tutorials/packaging-projects/#creating-the-package-files
137137
.. _Uploading the project to the Python Packaging Index: \
138-
https://packaging.python.org/tutorials/distributing-packages/#uploading-your-project-to-pypi
138+
https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives
139139
.. _The .pypirc file: \
140140
https://packaging.python.org/specifications/pypirc/
141141

Doc/library/binascii.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,23 @@ The :mod:`binascii` module defines the following functions:
5050
Added the *backtick* parameter.
5151

5252

53-
.. function:: a2b_base64(string)
53+
.. function:: a2b_base64(string, strict_mode=False)
5454

5555
Convert a block of base64 data back to binary and return the binary data. More
5656
than one line may be passed at a time.
5757

58+
If *strict_mode* is true, only valid base64 data will be converted. Invalid base64
59+
data will raise :exc:`binascii.Error`.
60+
61+
Valid base64:
62+
* Conforms to :rfc:`3548`.
63+
* Contains only characters from the base64 alphabet.
64+
* Contains no excess data after padding (including excess padding, newlines, etc.).
65+
* Does not start with a padding.
66+
67+
.. versionchanged:: 3.11
68+
Added the *strict_mode* parameter.
69+
5870

5971
.. function:: b2a_base64(data, *, newline=True)
6072

Lib/pdb.py

Lines changed: 104 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@
8080
import signal
8181
import inspect
8282
import tokenize
83+
import functools
8384
import traceback
8485
import linecache
8586

87+
from typing import Union
88+
8689

8790
class Restart(Exception):
8891
"""Causes a debugger to be restarted for the debugged python program."""
@@ -128,6 +131,77 @@ def __repr__(self):
128131
return self
129132

130133

134+
class ScriptTarget(str):
135+
def __new__(cls, val):
136+
# Mutate self to be the "real path".
137+
res = super().__new__(cls, os.path.realpath(val))
138+
139+
# Store the original path for error reporting.
140+
res.orig = val
141+
142+
return res
143+
144+
def check(self):
145+
if not os.path.exists(self):
146+
print('Error:', self.orig, 'does not exist')
147+
sys.exit(1)
148+
149+
# Replace pdb's dir with script's dir in front of module search path.
150+
sys.path[0] = os.path.dirname(self)
151+
152+
@property
153+
def filename(self):
154+
return self
155+
156+
@property
157+
def namespace(self):
158+
return dict(
159+
__name__='__main__',
160+
__file__=self,
161+
__builtins__=__builtins__,
162+
)
163+
164+
@property
165+
def code(self):
166+
with io.open(self) as fp:
167+
return f"exec(compile({fp.read()!r}, {self!r}, 'exec'))"
168+
169+
170+
class ModuleTarget(str):
171+
def check(self):
172+
pass
173+
174+
@functools.cached_property
175+
def _details(self):
176+
import runpy
177+
return runpy._get_module_details(self)
178+
179+
@property
180+
def filename(self):
181+
return self.code.co_filename
182+
183+
@property
184+
def code(self):
185+
name, spec, code = self._details
186+
return code
187+
188+
@property
189+
def _spec(self):
190+
name, spec, code = self._details
191+
return spec
192+
193+
@property
194+
def namespace(self):
195+
return dict(
196+
__name__='__main__',
197+
__file__=os.path.normcase(os.path.abspath(self.filename)),
198+
__package__=self._spec.parent,
199+
__loader__=self._spec.loader,
200+
__spec__=self._spec,
201+
__builtins__=__builtins__,
202+
)
203+
204+
131205
# Interaction prompt line will separate file and call info from code
132206
# text using value of line_prefix string. A newline and arrow may
133207
# be to your liking. You can set it once pdb is imported using the
@@ -1538,49 +1612,26 @@ def lookupmodule(self, filename):
15381612
return fullname
15391613
return None
15401614

1541-
def _runmodule(self, module_name):
1615+
def _run(self, target: Union[ModuleTarget, ScriptTarget]):
1616+
# When bdb sets tracing, a number of call and line events happen
1617+
# BEFORE debugger even reaches user's code (and the exact sequence of
1618+
# events depends on python version). Take special measures to
1619+
# avoid stopping before reaching the main script (see user_line and
1620+
# user_call for details).
15421621
self._wait_for_mainpyfile = True
15431622
self._user_requested_quit = False
1544-
import runpy
1545-
mod_name, mod_spec, code = runpy._get_module_details(module_name)
1546-
self.mainpyfile = self.canonic(code.co_filename)
1547-
import __main__
1548-
__main__.__dict__.clear()
1549-
__main__.__dict__.update({
1550-
"__name__": "__main__",
1551-
"__file__": self.mainpyfile,
1552-
"__package__": mod_spec.parent,
1553-
"__loader__": mod_spec.loader,
1554-
"__spec__": mod_spec,
1555-
"__builtins__": __builtins__,
1556-
})
1557-
self.run(code)
1558-
1559-
def _runscript(self, filename):
1560-
# The script has to run in __main__ namespace (or imports from
1561-
# __main__ will break).
1562-
#
1563-
# So we clear up the __main__ and set several special variables
1564-
# (this gets rid of pdb's globals and cleans old variables on restarts).
1623+
1624+
self.mainpyfile = self.canonic(target.filename)
1625+
1626+
# The target has to run in __main__ namespace (or imports from
1627+
# __main__ will break). Clear __main__ and replace with
1628+
# the target namespace.
15651629
import __main__
15661630
__main__.__dict__.clear()
1567-
__main__.__dict__.update({"__name__" : "__main__",
1568-
"__file__" : filename,
1569-
"__builtins__": __builtins__,
1570-
})
1631+
__main__.__dict__.update(target.namespace)
1632+
1633+
self.run(target.code)
15711634

1572-
# When bdb sets tracing, a number of call and line events happens
1573-
# BEFORE debugger even reaches user's code (and the exact sequence of
1574-
# events depends on python version). So we take special measures to
1575-
# avoid stopping before we reach the main script (see user_line and
1576-
# user_call for details).
1577-
self._wait_for_mainpyfile = True
1578-
self.mainpyfile = self.canonic(filename)
1579-
self._user_requested_quit = False
1580-
with io.open_code(filename) as fp:
1581-
statement = "exec(compile(%r, %r, 'exec'))" % \
1582-
(fp.read(), self.mainpyfile)
1583-
self.run(statement)
15841635

15851636
# Collect all command help into docstring, if not run with -OO
15861637

@@ -1669,6 +1720,7 @@ def help():
16691720
To let the script run up to a given line X in the debugged file, use
16701721
"-c 'until X'"."""
16711722

1723+
16721724
def main():
16731725
import getopt
16741726

@@ -1678,28 +1730,19 @@ def main():
16781730
print(_usage)
16791731
sys.exit(2)
16801732

1681-
commands = []
1682-
run_as_module = False
1683-
for opt, optarg in opts:
1684-
if opt in ['-h', '--help']:
1685-
print(_usage)
1686-
sys.exit()
1687-
elif opt in ['-c', '--command']:
1688-
commands.append(optarg)
1689-
elif opt in ['-m']:
1690-
run_as_module = True
1691-
1692-
mainpyfile = args[0] # Get script filename
1693-
if not run_as_module and not os.path.exists(mainpyfile):
1694-
print('Error:', mainpyfile, 'does not exist')
1695-
sys.exit(1)
1733+
if any(opt in ['-h', '--help'] for opt, optarg in opts):
1734+
print(_usage)
1735+
sys.exit()
16961736

1697-
sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list
1737+
commands = [optarg for opt, optarg in opts if opt in ['-c', '--command']]
16981738

1699-
if not run_as_module:
1700-
mainpyfile = os.path.realpath(mainpyfile)
1701-
# Replace pdb's dir with script's dir in front of module search path.
1702-
sys.path[0] = os.path.dirname(mainpyfile)
1739+
module_indicated = any(opt in ['-m'] for opt, optarg in opts)
1740+
cls = ModuleTarget if module_indicated else ScriptTarget
1741+
target = cls(args[0])
1742+
1743+
target.check()
1744+
1745+
sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list
17031746

17041747
# Note on saving/restoring sys.argv: it's a good idea when sys.argv was
17051748
# modified by the script being debugged. It's a bad idea when it was
@@ -1709,15 +1752,12 @@ def main():
17091752
pdb.rcLines.extend(commands)
17101753
while True:
17111754
try:
1712-
if run_as_module:
1713-
pdb._runmodule(mainpyfile)
1714-
else:
1715-
pdb._runscript(mainpyfile)
1755+
pdb._run(target)
17161756
if pdb._user_requested_quit:
17171757
break
17181758
print("The program finished and will be restarted")
17191759
except Restart:
1720-
print("Restarting", mainpyfile, "with arguments:")
1760+
print("Restarting", target, "with arguments:")
17211761
print("\t" + " ".join(sys.argv[1:]))
17221762
except SystemExit:
17231763
# In most cases SystemExit does not warrant a post-mortem session.
@@ -1732,7 +1772,7 @@ def main():
17321772
print("Running 'cont' or 'step' will restart the program")
17331773
t = sys.exc_info()[2]
17341774
pdb.interaction(None, t)
1735-
print("Post mortem debugger finished. The " + mainpyfile +
1775+
print("Post mortem debugger finished. The " + target +
17361776
" will be restarted")
17371777

17381778

Lib/test/test_binascii.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,47 @@ def addnoise(line):
114114
# empty strings. TBD: shouldn't it raise an exception instead ?
115115
self.assertEqual(binascii.a2b_base64(self.type2test(fillers)), b'')
116116

117+
def test_base64_strict_mode(self):
118+
# Test base64 with strict mode on
119+
def _assertRegexTemplate(assert_regex: str, data: bytes, non_strict_mode_expected_result: bytes):
120+
with self.assertRaisesRegex(binascii.Error, assert_regex):
121+
binascii.a2b_base64(self.type2test(data), strict_mode=True)
122+
self.assertEqual(binascii.a2b_base64(self.type2test(data), strict_mode=False),
123+
non_strict_mode_expected_result)
124+
self.assertEqual(binascii.a2b_base64(self.type2test(data)),
125+
non_strict_mode_expected_result)
126+
127+
def assertExcessData(data, non_strict_mode_expected_result: bytes):
128+
_assertRegexTemplate(r'(?i)Excess data', data, non_strict_mode_expected_result)
129+
130+
def assertNonBase64Data(data, non_strict_mode_expected_result: bytes):
131+
_assertRegexTemplate(r'(?i)Only base64 data', data, non_strict_mode_expected_result)
132+
133+
def assertMalformedPadding(data, non_strict_mode_expected_result: bytes):
134+
_assertRegexTemplate(r'(?i)Leading padding', data, non_strict_mode_expected_result)
135+
136+
# Test excess data exceptions
137+
assertExcessData(b'ab==a', b'i')
138+
assertExcessData(b'ab===', b'i')
139+
assertExcessData(b'ab==:', b'i')
140+
assertExcessData(b'abc=a', b'i\xb7')
141+
assertExcessData(b'abc=:', b'i\xb7')
142+
assertExcessData(b'ab==\n', b'i')
143+
144+
# Test non-base64 data exceptions
145+
assertNonBase64Data(b'\nab==', b'i')
146+
assertNonBase64Data(b'ab:(){:|:&};:==', b'i')
147+
assertNonBase64Data(b'a\nb==', b'i')
148+
assertNonBase64Data(b'a\x00b==', b'i')
149+
150+
# Test malformed padding
151+
assertMalformedPadding(b'=', b'')
152+
assertMalformedPadding(b'==', b'')
153+
assertMalformedPadding(b'===', b'')
154+
assertMalformedPadding(b'ab=c=', b'i\xb7')
155+
assertMalformedPadding(b'ab=ab==', b'i\xb6\x9b')
156+
157+
117158
def test_base64errors(self):
118159
# Test base64 with invalid padding
119160
def assertIncorrectPadding(data):

Lib/test/test_pdb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions():
362362
1 breakpoint keep yes at ...test_pdb.py:...
363363
2 breakpoint keep yes at ...test_pdb.py:...
364364
(Pdb) break pdb.find_function
365-
Breakpoint 3 at ...pdb.py:94
365+
Breakpoint 3 at ...pdb.py:97
366366
(Pdb) break
367367
Num Type Disp Enb Where
368368
1 breakpoint keep yes at ...test_pdb.py:...

Lib/test/test_pyclbr.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,11 @@ def test_others(self):
222222
cm('pickle', ignore=('partial', 'PickleBuffer'))
223223
cm('aifc', ignore=('_aifc_params',)) # set with = in module
224224
cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property
225-
cm('pdb')
225+
cm(
226+
'pdb',
227+
# pyclbr does not handle elegantly `typing` or properties
228+
ignore=('Union', 'ModuleTarget', 'ScriptTarget'),
229+
)
226230
cm('pydoc', ignore=('input', 'output',)) # properties
227231

228232
# Tests for modules inside packages
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Update of three expired hyperlinks in Doc/distributing/index.rst:
2+
"Project structure", "Building and packaging the project", and "Uploading the
3+
project to the Python Packaging Index".
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added a new optional :code:`strict_mode` parameter to *binascii.a2b_base64*.
2+
When :code:`scrict_mode` is set to :code:`True`, the *a2b_base64* function will accept only valid base64 content.
3+
More details about what "valid base64 content" is, can be found in the function's documentation.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor argument processing in :func:pdb.main to simplify detection of errors in input loading and clarify behavior around module or script invocation.

0 commit comments

Comments
 (0)