diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 1eaf1cc5d9a68e..892f5ba9a7624e 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -374,6 +374,13 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Return ``True`` if the object is a bound method written in Python. +.. function:: ispackage(object) + + Return ``True`` if the object is a :term:`package`. + + .. versionadded:: 3.14 + + .. function:: isfunction(object) Return ``True`` if the object is a Python function, which includes functions diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index a6e2e3b8928ebe..704637d675ead3 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -3889,6 +3889,9 @@ copying. .. versionchanged:: 3.5 memoryviews can now be indexed with tuple of integers. + .. versionchanged:: next + memoryview is now a :term:`generic type`. + :class:`memoryview` has several methods: .. method:: __eq__(exporter) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index d95f1848ad6d86..a6f595ccf08bf4 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -194,6 +194,10 @@ Other language changes :mod:`copyable `. (Contributed by Serhiy Storchaka in :gh:`125767`.) +* The :class:`memoryview` type now supports subscription, + making it a :term:`generic type`. + (Contributed by Brian Schubert in :gh:`126012`.) + New modules =========== @@ -326,6 +330,10 @@ inspect If true, string :term:`annotations ` are displayed without surrounding quotes. (Contributed by Jelle Zijlstra in :gh:`101552`.) +* Add function :func:`inspect.ispackage` to determine whether an object is a + :term:`package` or not. + (Contributed by Zhikang Yan in :gh:`125634`.) + json ---- diff --git a/Lib/code.py b/Lib/code.py index c7c59ee20219c5..1cc2ed8b1dbf28 100644 --- a/Lib/code.py +++ b/Lib/code.py @@ -136,7 +136,8 @@ def _showtraceback(self, typ, value, tb, source): # Set the line of text that the exception refers to lines = source.splitlines() if (source and typ is SyntaxError - and not value.text and len(lines) >= value.lineno): + and not value.text and value.lineno is not None + and len(lines) >= value.lineno): value.text = lines[value.lineno - 1] sys.last_exc = sys.last_value = value if sys.excepthook is sys.__excepthook__: diff --git a/Lib/inspect.py b/Lib/inspect.py index 0c33c6cc995a03..ea0d992436eb17 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -6,9 +6,9 @@ Here are some of the useful functions provided by this module: - ismodule(), isclass(), ismethod(), isfunction(), isgeneratorfunction(), - isgenerator(), istraceback(), isframe(), iscode(), isbuiltin(), - isroutine() - check object types + ismodule(), isclass(), ismethod(), ispackage(), isfunction(), + isgeneratorfunction(), isgenerator(), istraceback(), isframe(), + iscode(), isbuiltin(), isroutine() - check object types getmembers() - get members of an object that satisfy a given condition getfile(), getsourcefile(), getsource() - find an object's source code @@ -128,6 +128,7 @@ "ismethoddescriptor", "ismethodwrapper", "ismodule", + "ispackage", "isroutine", "istraceback", "markcoroutinefunction", @@ -186,6 +187,10 @@ def ismethod(object): """Return true if the object is an instance method.""" return isinstance(object, types.MethodType) +def ispackage(object): + """Return true if the object is a package.""" + return ismodule(object) and hasattr(object, "__path__") + def ismethoddescriptor(object): """Return true if the object is a method descriptor. diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index b9021467817f27..6df09d891433ea 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -567,6 +567,17 @@ def hook(event, args): _winapi.CreateNamedPipe(pipe_name, _winapi.PIPE_ACCESS_DUPLEX, 8, 2, 0, 0, 0, 0) +def test_assert_unicode(): + import sys + sys.addaudithook(lambda *args: None) + try: + sys.audit(9) + except TypeError: + pass + else: + raise RuntimeError("Expected sys.audit(9) to fail.") + + if __name__ == "__main__": from test.support import suppress_msvcrt_asserts diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index ff917c98ed8420..7c1ef42a4970d7 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -520,6 +520,11 @@ def requires_debug_ranges(reason='requires co_positions / debug_ranges'): is_android = sys.platform == "android" +def skip_android_selinux(name): + return unittest.skipIf( + sys.platform == "android", f"Android blocks {name} with SELinux" + ) + if sys.platform not in {"win32", "vxworks", "ios", "tvos", "watchos"}: unix_shell = '/system/bin/sh' if is_android else '/bin/sh' else: diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 20ebb8e6bef880..3a4291e3a68ca6 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -32,6 +32,25 @@ def last_cb(): pass +class ReachableCode(Exception): + """Exception to raise to indicate that some code was reached. + + Use this exception if using mocks is not a good alternative. + """ + + +class SimpleEvilEventLoop(asyncio.base_events.BaseEventLoop): + """Base class for UAF and other evil stuff requiring an evil event loop.""" + + def get_debug(self): # to suppress tracebacks + return False + + def __del__(self): + # Automatically close the evil event loop to avoid warnings. + if not self.is_closed() and not self.is_running(): + self.close() + + class DuckFuture: # Class that does not inherit from Future but aims to be duck-type # compatible with it. @@ -948,6 +967,7 @@ def __eq__(self, other): fut.remove_done_callback(evil()) def test_evil_call_soon_list_mutation(self): + # see: https://github.com/python/cpython/issues/125969 called_on_fut_callback0 = False pad = lambda: ... @@ -962,9 +982,8 @@ def evil_call_soon(*args, **kwargs): else: called_on_fut_callback0 = True - fake_event_loop = lambda: ... + fake_event_loop = SimpleEvilEventLoop() fake_event_loop.call_soon = evil_call_soon - fake_event_loop.get_debug = lambda: False # suppress traceback with mock.patch.object(self, 'loop', fake_event_loop): fut = self._new_future() @@ -998,6 +1017,56 @@ def __eq__(self, other): fut.add_done_callback(cb_pad()) fut.remove_done_callback(evil()) + def test_use_after_free_on_fut_callback_0_with_evil__getattribute__(self): + # see: https://github.com/python/cpython/issues/125984 + + class EvilEventLoop(SimpleEvilEventLoop): + def call_soon(self, *args, **kwargs): + super().call_soon(*args, **kwargs) + raise ReachableCode + + def __getattribute__(self, name): + nonlocal fut_callback_0 + if name == 'call_soon': + fut.remove_done_callback(fut_callback_0) + del fut_callback_0 + return object.__getattribute__(self, name) + + evil_loop = EvilEventLoop() + with mock.patch.object(self, 'loop', evil_loop): + fut = self._new_future() + self.assertIs(fut.get_loop(), evil_loop) + + fut_callback_0 = lambda: ... + fut.add_done_callback(fut_callback_0) + self.assertRaises(ReachableCode, fut.set_result, "boom") + + def test_use_after_free_on_fut_context_0_with_evil__getattribute__(self): + # see: https://github.com/python/cpython/issues/125984 + + class EvilEventLoop(SimpleEvilEventLoop): + def call_soon(self, *args, **kwargs): + super().call_soon(*args, **kwargs) + raise ReachableCode + + def __getattribute__(self, name): + if name == 'call_soon': + # resets the future's event loop + fut.__init__(loop=SimpleEvilEventLoop()) + return object.__getattribute__(self, name) + + evil_loop = EvilEventLoop() + with mock.patch.object(self, 'loop', evil_loop): + fut = self._new_future() + self.assertIs(fut.get_loop(), evil_loop) + + fut_callback_0 = mock.Mock() + fut_context_0 = mock.Mock() + fut.add_done_callback(fut_callback_0, context=fut_context_0) + del fut_context_0 + del fut_callback_0 + self.assertRaises(ReachableCode, fut.set_result, "boom") + @unittest.skipUnless(hasattr(futures, '_CFuture'), 'requires the C _asyncio module') diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index 7206307d8b0664..ddd9f951143df7 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -307,5 +307,12 @@ def test_winapi_createnamedpipe(self): self.assertEqual(actual, expected) + def test_assert_unicode(self): + # See gh-126018 + returncode, _, stderr = self.run_python("test_assert_unicode") + if returncode: + self.fail(stderr) + + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_genericalias.py b/Lib/test/test_genericalias.py index 12564b423493aa..3048d038c782d4 100644 --- a/Lib/test/test_genericalias.py +++ b/Lib/test/test_genericalias.py @@ -100,7 +100,7 @@ class BaseTest(unittest.TestCase): """Test basics.""" - generic_types = [type, tuple, list, dict, set, frozenset, enumerate, + generic_types = [type, tuple, list, dict, set, frozenset, enumerate, memoryview, defaultdict, deque, SequenceMatcher, dircmp, diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 77fdc6f238437e..a4857dadec2d5c 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -51,7 +51,7 @@ # Functions tested in this suite: # ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode, -# isbuiltin, isroutine, isgenerator, isgeneratorfunction, getmembers, +# isbuiltin, isroutine, isgenerator, ispackage, isgeneratorfunction, getmembers, # getdoc, getfile, getmodule, getsourcefile, getcomments, getsource, # getclasstree, getargvalues, formatargvalues, currentframe, # stack, trace, ismethoddescriptor, isdatadescriptor, ismethodwrapper @@ -105,7 +105,7 @@ def unsorted_keyword_only_parameters_fn(*, throw, out, the, baby, with_, class IsTestBase(unittest.TestCase): predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, inspect.isframe, inspect.isfunction, inspect.ismethod, - inspect.ismodule, inspect.istraceback, + inspect.ismodule, inspect.istraceback, inspect.ispackage, inspect.isgenerator, inspect.isgeneratorfunction, inspect.iscoroutine, inspect.iscoroutinefunction, inspect.isasyncgen, inspect.isasyncgenfunction, @@ -121,7 +121,10 @@ def istest(self, predicate, exp): predicate == inspect.iscoroutinefunction) and \ other == inspect.isfunction: continue - self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) + if predicate == inspect.ispackage and other == inspect.ismodule: + self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp)) + else: + self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) def test__all__(self): support.check__all__(self, inspect, not_exported=("modulesbyfile",), extra=("get_annotations",)) @@ -201,7 +204,17 @@ def test_excluding_predicates(self): self.assertFalse(inspect.ismethodwrapper(int)) self.assertFalse(inspect.ismethodwrapper(type("AnyClass", (), {}))) + def test_ispackage(self): + self.istest(inspect.ispackage, 'asyncio') + self.istest(inspect.ispackage, 'importlib') + self.assertFalse(inspect.ispackage(inspect)) + self.assertFalse(inspect.ispackage(mod)) + self.assertFalse(inspect.ispackage(':)')) + + class FakePackage: + __path__ = None + self.assertFalse(inspect.ispackage(FakePackage())) def test_iscoroutine(self): async_gen_coro = async_generator_function_example(1) diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 0c6df4e5dae869..e0ee310e2c4dbc 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -117,6 +117,15 @@ def test_runsource_shows_syntax_error_for_failed_compilation(self): console.runsource(source) mock_showsyntaxerror.assert_called_once() + def test_runsource_survives_null_bytes(self): + console = InteractiveColoredConsole() + source = "\x00\n" + f = io.StringIO() + with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f): + result = console.runsource(source) + self.assertFalse(result) + self.assertIn("source code string cannot contain null bytes", f.getvalue()) + def test_no_active_future(self): console = InteractiveColoredConsole() source = dedent("""\ diff --git a/Lib/test/test_pyrepl/test_pyrepl.py b/Lib/test/test_pyrepl/test_pyrepl.py index 1a76832386bf1d..f29a7ffbd7cafd 100644 --- a/Lib/test/test_pyrepl/test_pyrepl.py +++ b/Lib/test/test_pyrepl/test_pyrepl.py @@ -1313,6 +1313,11 @@ def test_proper_tracebacklimit(self): self.assertIn("in x3", output) self.assertIn("in ", output) + def test_null_byte(self): + output, exit_code = self.run_repl("\x00\nexit()\n") + self.assertEqual(exit_code, 0) + self.assertNotIn("TypeError", output) + def test_readline_history_file(self): # skip, if readline module is not available readline = import_module('readline') diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 663fa50c086c13..f2bc52ba6e8701 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1111,6 +1111,7 @@ def test_sethostname(self): @unittest.skipUnless(hasattr(socket, 'if_nameindex'), 'socket.if_nameindex() not available.') + @support.skip_android_selinux('if_nameindex') def testInterfaceNameIndex(self): interfaces = socket.if_nameindex() for index, name in interfaces: @@ -1127,6 +1128,7 @@ def testInterfaceNameIndex(self): @unittest.skipUnless(hasattr(socket, 'if_indextoname'), 'socket.if_indextoname() not available.') + @support.skip_android_selinux('if_indextoname') def testInvalidInterfaceIndexToName(self): self.assertRaises(OSError, socket.if_indextoname, 0) self.assertRaises(OverflowError, socket.if_indextoname, -1) @@ -1146,6 +1148,7 @@ def testInvalidInterfaceIndexToName(self): @unittest.skipUnless(hasattr(socket, 'if_nametoindex'), 'socket.if_nametoindex() not available.') + @support.skip_android_selinux('if_nametoindex') def testInvalidInterfaceNameToIndex(self): self.assertRaises(TypeError, socket.if_nametoindex, 0) self.assertRaises(OSError, socket.if_nametoindex, '_DEADBEEF') @@ -1878,6 +1881,7 @@ def test_getfqdn_filter_localhost(self): @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') @unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()") + @support.skip_android_selinux('if_nameindex') def test_getaddrinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface (Linux, Mac OS X) (ifindex, test_interface) = socket.if_nameindex()[0] @@ -1911,6 +1915,7 @@ def test_getaddrinfo_ipv6_scopeid_numeric(self): @unittest.skipIf(sys.platform == 'win32', 'does not work on Windows') @unittest.skipIf(AIX, 'Symbolic scope id does not work') @unittest.skipUnless(hasattr(socket, 'if_nameindex'), "test needs socket.if_nameindex()") + @support.skip_android_selinux('if_nameindex') def test_getnameinfo_ipv6_scopeid_symbolic(self): # Just pick up any network interface. (ifindex, test_interface) = socket.if_nameindex()[0] diff --git a/Lib/test/test_termios.py b/Lib/test/test_termios.py index b98cbd553dac3f..d6eb00175b4c85 100644 --- a/Lib/test/test_termios.py +++ b/Lib/test/test_termios.py @@ -3,6 +3,7 @@ import sys import tempfile import unittest +from test import support from test.support.import_helper import import_module termios = import_module('termios') @@ -18,10 +19,16 @@ def setUp(self): tmp = self.enterContext(tempfile.TemporaryFile(mode='wb', buffering=0)) self.bad_fd = tmp.fileno() - def assertRaisesTermiosError(self, errno, callable, *args): + def assertRaisesTermiosError(self, err, callable, *args): + # Some versions of Android return EACCES when calling termios functions + # on a regular file. + errs = [err] + if sys.platform == 'android' and err == errno.ENOTTY: + errs.append(errno.EACCES) + with self.assertRaises(termios.error) as cm: callable(*args) - self.assertEqual(cm.exception.args[0], errno) + self.assertIn(cm.exception.args[0], errs) def test_tcgetattr(self): attrs = termios.tcgetattr(self.fd) @@ -90,6 +97,7 @@ def test_tcsetattr_errors(self): self.assertRaises(TypeError, termios.tcsetattr, object(), termios.TCSANOW, attrs) self.assertRaises(TypeError, termios.tcsetattr, self.fd, termios.TCSANOW) + @support.skip_android_selinux('tcsendbreak') def test_tcsendbreak(self): try: termios.tcsendbreak(self.fd, 1) @@ -100,6 +108,7 @@ def test_tcsendbreak(self): raise termios.tcsendbreak(self.stream, 1) + @support.skip_android_selinux('tcsendbreak') def test_tcsendbreak_errors(self): self.assertRaises(OverflowError, termios.tcsendbreak, self.fd, 2**1000) self.assertRaises(TypeError, termios.tcsendbreak, self.fd, 0.0) @@ -110,10 +119,12 @@ def test_tcsendbreak_errors(self): self.assertRaises(TypeError, termios.tcsendbreak, object(), 0) self.assertRaises(TypeError, termios.tcsendbreak, self.fd) + @support.skip_android_selinux('tcdrain') def test_tcdrain(self): termios.tcdrain(self.fd) termios.tcdrain(self.stream) + @support.skip_android_selinux('tcdrain') def test_tcdrain_errors(self): self.assertRaisesTermiosError(errno.ENOTTY, termios.tcdrain, self.bad_fd) self.assertRaises(ValueError, termios.tcdrain, -1) @@ -136,12 +147,14 @@ def test_tcflush_errors(self): self.assertRaises(TypeError, termios.tcflush, object(), termios.TCIFLUSH) self.assertRaises(TypeError, termios.tcflush, self.fd) + @support.skip_android_selinux('tcflow') def test_tcflow(self): termios.tcflow(self.fd, termios.TCOOFF) termios.tcflow(self.fd, termios.TCOON) termios.tcflow(self.fd, termios.TCIOFF) termios.tcflow(self.fd, termios.TCION) + @support.skip_android_selinux('tcflow') def test_tcflow_errors(self): self.assertRaisesTermiosError(errno.EINVAL, termios.tcflow, self.fd, -1) self.assertRaises(OverflowError, termios.tcflow, self.fd, 2**1000) diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 77ef0c5b3c480d..ec69412f5511eb 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4637,6 +4637,49 @@ def foo(): f'{boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}'] self.assertEqual(actual, expected) + def test_colorized_traceback_from_exception_group(self): + def foo(): + exceptions = [] + try: + 1 / 0 + except ZeroDivisionError as inner_exc: + exceptions.append(inner_exc) + raise ExceptionGroup("test", exceptions) + + try: + foo() + except Exception as e: + exc = traceback.TracebackException.from_exception( + e, capture_locals=True + ) + + red = _colorize.ANSIColors.RED + boldr = _colorize.ANSIColors.BOLD_RED + magenta = _colorize.ANSIColors.MAGENTA + boldm = _colorize.ANSIColors.BOLD_MAGENTA + reset = _colorize.ANSIColors.RESET + lno_foo = foo.__code__.co_firstlineno + actual = "".join(exc.format(colorize=True)).splitlines() + expected = [f" + Exception Group Traceback (most recent call last):", + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+9}{reset}, in {magenta}test_colorized_traceback_from_exception_group{reset}', + f' | {red}foo{reset}{boldr}(){reset}', + f' | {red}~~~{reset}{boldr}^^{reset}', + f" | e = ExceptionGroup('test', [ZeroDivisionError('division by zero')])", + f" | foo = {foo}", + f' | self = <{__name__}.TestColorizedTraceback testMethod=test_colorized_traceback_from_exception_group>', + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+6}{reset}, in {magenta}foo{reset}', + f' | raise ExceptionGroup("test", exceptions)', + f" | exceptions = [ZeroDivisionError('division by zero')]", + f' | {boldm}ExceptionGroup{reset}: {magenta}test (1 sub-exception){reset}', + f' +-+---------------- 1 ----------------', + f' | Traceback (most recent call last):', + f' | File {magenta}"{__file__}"{reset}, line {magenta}{lno_foo+3}{reset}, in {magenta}foo{reset}', + f' | {red}1 {reset}{boldr}/{reset}{red} 0{reset}', + f' | {red}~~{reset}{boldr}^{reset}{red}~~{reset}', + f" | exceptions = [ZeroDivisionError('division by zero')]", + f' | {boldm}ZeroDivisionError{reset}: {magenta}division by zero{reset}', + f' +------------------------------------'] + self.assertEqual(actual, expected) if __name__ == "__main__": unittest.main() diff --git a/Lib/traceback.py b/Lib/traceback.py index 0fe7187a0c6193..f73149271b9bc9 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1428,7 +1428,7 @@ def format(self, *, chain=True, _ctx=None, **kwargs): f'+---------------- {title} ----------------\n') _ctx.exception_group_depth += 1 if not truncated: - yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx) + yield from exc.exceptions[i].format(chain=chain, _ctx=_ctx, colorize=colorize) else: remaining = num_excs - self.max_group_width plural = 's' if remaining > 1 else '' diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst b/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst new file mode 100644 index 00000000000000..220e94467af849 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-10-18-10-11-43.gh-issue-125593.Q97m3A.rst @@ -0,0 +1 @@ +Use color to highlight error locations in traceback from exception group diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-10-26-13-32-48.gh-issue-126012.2KalhG.rst b/Misc/NEWS.d/next/Core and Builtins/2024-10-26-13-32-48.gh-issue-126012.2KalhG.rst new file mode 100644 index 00000000000000..5307920ddcf200 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-10-26-13-32-48.gh-issue-126012.2KalhG.rst @@ -0,0 +1,2 @@ +The :class:`memoryview` type now supports subscription, making it a +:term:`generic type`. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-10-26-23-50-03.gh-issue-126018.Hq-qcM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-26-23-50-03.gh-issue-126018.Hq-qcM.rst new file mode 100644 index 00000000000000..e019408638997b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-10-26-23-50-03.gh-issue-126018.Hq-qcM.rst @@ -0,0 +1,2 @@ +Fix a crash in :func:`sys.audit` when passing a non-string as first argument +and Python was compiled in debug mode. diff --git a/Misc/NEWS.d/next/Library/2024-10-17-04-52-00.gh-issue-125633.lMck06.rst b/Misc/NEWS.d/next/Library/2024-10-17-04-52-00.gh-issue-125633.lMck06.rst new file mode 100644 index 00000000000000..e816a13b75e0c7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-17-04-52-00.gh-issue-125633.lMck06.rst @@ -0,0 +1,2 @@ +Add function :func:`inspect.ispackage` to determine whether an object is a +:term:`package` or not. diff --git a/Misc/NEWS.d/next/Library/2024-10-19-16-06-52.gh-issue-125666.jGfdCP.rst b/Misc/NEWS.d/next/Library/2024-10-19-16-06-52.gh-issue-125666.jGfdCP.rst new file mode 100644 index 00000000000000..3b4488815cced6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-19-16-06-52.gh-issue-125666.jGfdCP.rst @@ -0,0 +1 @@ +Avoid the exiting the interpreter if a null byte is given as input in the new REPL. diff --git a/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst b/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst new file mode 100644 index 00000000000000..7a1d7b53b11301 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-10-26-12-50-48.gh-issue-125984.d4vp5_.rst @@ -0,0 +1,3 @@ +Fix use-after-free crashes on :class:`asyncio.Future` objects for which the +underlying event loop implements an evil :meth:`~object.__getattribute__`. +Reported by Nico-Posada. Patch by Bénédikt Tran. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 204cf34b23d789..d4135f04e56575 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -409,12 +409,19 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut) if (fut->fut_callback0 != NULL) { /* There's a 1st callback */ - int ret = call_soon(state, - fut->fut_loop, fut->fut_callback0, - (PyObject *)fut, fut->fut_context0); - - Py_CLEAR(fut->fut_callback0); - Py_CLEAR(fut->fut_context0); + // Beware: An evil call_soon could alter fut_callback0 or fut_context0. + // Since we are anyway clearing them after the call, whether call_soon + // succeeds or not, the idea is to transfer ownership so that external + // code is not able to alter them during the call. + PyObject *fut_callback0 = fut->fut_callback0; + fut->fut_callback0 = NULL; + PyObject *fut_context0 = fut->fut_context0; + fut->fut_context0 = NULL; + + int ret = call_soon(state, fut->fut_loop, fut_callback0, + (PyObject *)fut, fut_context0); + Py_CLEAR(fut_callback0); + Py_CLEAR(fut_context0); if (ret) { /* If an error occurs in pure-Python implementation, all callbacks are cleared. */ diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index d9d919ea75d853..1857fca736ef20 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4404,7 +4404,8 @@ os_sched_getscheduler(PyObject *module, PyObject *arg) PyObject *return_value = NULL; pid_t pid; - if (!PyArg_Parse(arg, "" _Py_PARSE_PID ":sched_getscheduler", &pid)) { + pid = PyLong_AsPid(arg); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_sched_getscheduler_impl(module, pid); @@ -4502,10 +4503,18 @@ os_sched_setscheduler(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int policy; PyObject *param_obj; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "iO:sched_setscheduler", - &pid, &policy, ¶m_obj)) { + if (!_PyArg_CheckPositional("sched_setscheduler", nargs, 3, 3)) { goto exit; } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + policy = PyLong_AsInt(args[1]); + if (policy == -1 && PyErr_Occurred()) { + goto exit; + } + param_obj = args[2]; return_value = os_sched_setscheduler_impl(module, pid, policy, param_obj); exit: @@ -4537,7 +4546,8 @@ os_sched_getparam(PyObject *module, PyObject *arg) PyObject *return_value = NULL; pid_t pid; - if (!PyArg_Parse(arg, "" _Py_PARSE_PID ":sched_getparam", &pid)) { + pid = PyLong_AsPid(arg); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_sched_getparam_impl(module, pid); @@ -4572,10 +4582,14 @@ os_sched_setparam(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pid; PyObject *param_obj; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "O:sched_setparam", - &pid, ¶m_obj)) { + if (!_PyArg_CheckPositional("sched_setparam", nargs, 2, 2)) { goto exit; } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + param_obj = args[1]; return_value = os_sched_setparam_impl(module, pid, param_obj); exit: @@ -4607,7 +4621,8 @@ os_sched_rr_get_interval(PyObject *module, PyObject *arg) pid_t pid; double _return_value; - if (!PyArg_Parse(arg, "" _Py_PARSE_PID ":sched_rr_get_interval", &pid)) { + pid = PyLong_AsPid(arg); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } _return_value = os_sched_rr_get_interval_impl(module, pid); @@ -4667,10 +4682,14 @@ os_sched_setaffinity(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pid; PyObject *mask; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "O:sched_setaffinity", - &pid, &mask)) { + if (!_PyArg_CheckPositional("sched_setaffinity", nargs, 2, 2)) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } + mask = args[1]; return_value = os_sched_setaffinity_impl(module, pid, mask); exit: @@ -4701,7 +4720,8 @@ os_sched_getaffinity(PyObject *module, PyObject *arg) PyObject *return_value = NULL; pid_t pid; - if (!PyArg_Parse(arg, "" _Py_PARSE_PID ":sched_getaffinity", &pid)) { + pid = PyLong_AsPid(arg); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_sched_getaffinity_impl(module, pid); @@ -5300,14 +5320,19 @@ os_getpgid(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * static const char * const _keywords[] = {"pid", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .format = "" _Py_PARSE_PID ":getpgid", + .fname = "getpgid", .kwtuple = KWTUPLE, }; #undef KWTUPLE + PyObject *argsbuf[1]; pid_t pid; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &pid)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_getpgid_impl(module, pid); @@ -5452,10 +5477,25 @@ os_kill(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pid; Py_ssize_t signal; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "n:kill", - &pid, &signal)) { + if (!_PyArg_CheckPositional("kill", nargs, 2, 2)) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[1]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + signal = ival; + } return_value = os_kill_impl(module, pid, signal); exit: @@ -5485,8 +5525,15 @@ os_killpg(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pgid; int signal; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "i:killpg", - &pgid, &signal)) { + if (!_PyArg_CheckPositional("killpg", nargs, 2, 2)) { + goto exit; + } + pgid = PyLong_AsPid(args[0]); + if (pgid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + signal = PyLong_AsInt(args[1]); + if (signal == -1 && PyErr_Occurred()) { goto exit; } return_value = os_killpg_impl(module, pgid, signal); @@ -5849,15 +5896,24 @@ os_wait4(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw static const char * const _keywords[] = {"pid", "options", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .format = "" _Py_PARSE_PID "i:wait4", + .fname = "wait4", .kwtuple = KWTUPLE, }; #undef KWTUPLE + PyObject *argsbuf[2]; pid_t pid; int options; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &pid, &options)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); + if (!args) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + options = PyLong_AsInt(args[1]); + if (options == -1 && PyErr_Occurred()) { goto exit; } return_value = os_wait4_impl(module, pid, options); @@ -5901,8 +5957,18 @@ os_waitid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) id_t id; int options; - if (!_PyArg_ParseStack(args, nargs, "i" _Py_PARSE_PID "i:waitid", - &idtype, &id, &options)) { + if (!_PyArg_CheckPositional("waitid", nargs, 3, 3)) { + goto exit; + } + if (!idtype_t_converter(args[0], &idtype)) { + goto exit; + } + id = (id_t)PyLong_AsPid(args[1]); + if (id == (id_t)(-1) && PyErr_Occurred()) { + goto exit; + } + options = PyLong_AsInt(args[2]); + if (options == -1 && PyErr_Occurred()) { goto exit; } return_value = os_waitid_impl(module, idtype, id, options); @@ -5939,8 +6005,15 @@ os_waitpid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pid; int options; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "i:waitpid", - &pid, &options)) { + if (!_PyArg_CheckPositional("waitpid", nargs, 2, 2)) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + options = PyLong_AsInt(args[1]); + if (options == -1 && PyErr_Occurred()) { goto exit; } return_value = os_waitpid_impl(module, pid, options); @@ -5977,8 +6050,15 @@ os_waitpid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) intptr_t pid; int options; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_INTPTR "i:waitpid", - &pid, &options)) { + if (!_PyArg_CheckPositional("waitpid", nargs, 2, 2)) { + goto exit; + } + pid = (intptr_t)PyLong_AsVoidPtr(args[0]); + if (!pid && PyErr_Occurred()) { + goto exit; + } + options = PyLong_AsInt(args[1]); + if (options == -1 && PyErr_Occurred()) { goto exit; } return_value = os_waitpid_impl(module, pid, options); @@ -6056,17 +6136,30 @@ os_pidfd_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec static const char * const _keywords[] = {"pid", "flags", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .format = "" _Py_PARSE_PID "|O&:pidfd_open", + .fname = "pidfd_open", .kwtuple = KWTUPLE, }; #undef KWTUPLE + PyObject *argsbuf[2]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; pid_t pid; unsigned int flags = 0; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &pid, _PyLong_UnsignedInt_Converter, &flags)) { + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf); + if (!args) { goto exit; } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + if (!_PyLong_UnsignedInt_Converter(args[1], &flags)) { + goto exit; + } +skip_optional_pos: return_value = os_pidfd_open_impl(module, pid, flags); exit: @@ -6816,7 +6909,8 @@ os_getsid(PyObject *module, PyObject *arg) PyObject *return_value = NULL; pid_t pid; - if (!PyArg_Parse(arg, "" _Py_PARSE_PID ":getsid", &pid)) { + pid = PyLong_AsPid(arg); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_getsid_impl(module, pid); @@ -6870,8 +6964,15 @@ os_setpgid(PyObject *module, PyObject *const *args, Py_ssize_t nargs) pid_t pid; pid_t pgrp; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_PID "" _Py_PARSE_PID ":setpgid", - &pid, &pgrp)) { + if (!_PyArg_CheckPositional("setpgid", nargs, 2, 2)) { + goto exit; + } + pid = PyLong_AsPid(args[0]); + if (pid == (pid_t)(-1) && PyErr_Occurred()) { + goto exit; + } + pgrp = PyLong_AsPid(args[1]); + if (pgrp == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_setpgid_impl(module, pid, pgrp); @@ -6935,8 +7036,15 @@ os_tcsetpgrp(PyObject *module, PyObject *const *args, Py_ssize_t nargs) int fd; pid_t pgid; - if (!_PyArg_ParseStack(args, nargs, "i" _Py_PARSE_PID ":tcsetpgrp", - &fd, &pgid)) { + if (!_PyArg_CheckPositional("tcsetpgrp", nargs, 2, 2)) { + goto exit; + } + fd = PyLong_AsInt(args[0]); + if (fd == -1 && PyErr_Occurred()) { + goto exit; + } + pgid = PyLong_AsPid(args[1]); + if (pgid == (pid_t)(-1) && PyErr_Occurred()) { goto exit; } return_value = os_tcsetpgrp_impl(module, fd, pgid); @@ -11318,7 +11426,8 @@ os_get_handle_inheritable(PyObject *module, PyObject *arg) intptr_t handle; int _return_value; - if (!PyArg_Parse(arg, "" _Py_PARSE_INTPTR ":get_handle_inheritable", &handle)) { + handle = (intptr_t)PyLong_AsVoidPtr(arg); + if (!handle && PyErr_Occurred()) { goto exit; } _return_value = os_get_handle_inheritable_impl(module, handle); @@ -11355,8 +11464,15 @@ os_set_handle_inheritable(PyObject *module, PyObject *const *args, Py_ssize_t na intptr_t handle; int inheritable; - if (!_PyArg_ParseStack(args, nargs, "" _Py_PARSE_INTPTR "p:set_handle_inheritable", - &handle, &inheritable)) { + if (!_PyArg_CheckPositional("set_handle_inheritable", nargs, 2, 2)) { + goto exit; + } + handle = (intptr_t)PyLong_AsVoidPtr(args[0]); + if (!handle && PyErr_Occurred()) { + goto exit; + } + inheritable = PyObject_IsTrue(args[1]); + if (inheritable < 0) { goto exit; } return_value = os_set_handle_inheritable_impl(module, handle, inheritable); @@ -12897,4 +13013,4 @@ os__create_environ(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #define OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF #endif /* !defined(OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF) */ -/*[clinic end generated code: output=18d75b737513dae6 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9756767bdbdabe94 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index c0af78ba075e85..bb5077cc7f0f09 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1550,6 +1550,19 @@ dir_fd_and_follow_symlinks_invalid(const char *function_name, int dir_fd, return 0; } +#if defined(HAVE_WAITID) +static int +idtype_t_converter(PyObject *arg, void *addr) +{ + int value = PyLong_AsInt(arg); + if (value == -1 && PyErr_Occurred()) { + return 0; + } + *((idtype_t *)addr) = (idtype_t)(value); + return 1; +} +#endif + #ifdef MS_WINDOWS typedef long long Py_off_t; #else @@ -3054,17 +3067,42 @@ class pid_t_converter(CConverter): type = 'pid_t' format_unit = '" _Py_PARSE_PID "' -class idtype_t_converter(int_converter): + def parse_arg(self, argname, displayname, *, limited_capi): + return self.format_code(""" + {paramname} = PyLong_AsPid({argname}); + if ({paramname} == (pid_t)(-1) && PyErr_Occurred()) {{{{ + goto exit; + }}}} + """, argname=argname) + +class idtype_t_converter(CConverter): type = 'idtype_t' + converter = 'idtype_t_converter' class id_t_converter(CConverter): type = 'id_t' format_unit = '" _Py_PARSE_PID "' + def parse_arg(self, argname, displayname, *, limited_capi): + return self.format_code(""" + {paramname} = (id_t)PyLong_AsPid({argname}); + if ({paramname} == (id_t)(-1) && PyErr_Occurred()) {{{{ + goto exit; + }}}} + """, argname=argname) + class intptr_t_converter(CConverter): type = 'intptr_t' format_unit = '" _Py_PARSE_INTPTR "' + def parse_arg(self, argname, displayname, *, limited_capi): + return self.format_code(""" + {paramname} = (intptr_t)PyLong_AsVoidPtr({argname}); + if (!{paramname} && PyErr_Occurred()) {{{{ + goto exit; + }}}} + """, argname=argname) + class Py_off_t_converter(CConverter): type = 'Py_off_t' converter = 'Py_off_t_converter' @@ -3084,7 +3122,7 @@ class sysconf_confname_converter(path_confname_converter): converter="conv_sysconf_confname" [python start generated code]*/ -/*[python end generated code: output=da39a3ee5e6b4b0d input=577cb476e5d64960]*/ +/*[python end generated code: output=da39a3ee5e6b4b0d input=1860d32584c2a539]*/ /*[clinic input] diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index a2472d4873641d..d4672e8198cb24 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3286,6 +3286,7 @@ static PyMethodDef memory_methods[] = { MEMORYVIEW__FROM_FLAGS_METHODDEF {"__enter__", memory_enter, METH_NOARGS, NULL}, {"__exit__", memory_exit, METH_VARARGS, memory_exit_doc}, + {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("See PEP 585")}, {NULL, NULL} }; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 8b9209324002ce..24af4798eeac3b 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -519,7 +519,6 @@ sys_audit(PyObject *self, PyObject *const *args, Py_ssize_t argc) } assert(args[0] != NULL); - assert(PyUnicode_Check(args[0])); if (!should_audit(tstate->interp)) { Py_RETURN_NONE;