diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index cad49e2deeb46f..448f2725e9a3db 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -369,8 +369,8 @@ Miscellaneous options .. option:: -R Turn on hash randomization. This option only has an effect if the - :envvar:`PYTHONHASHSEED` environment variable is set to ``0``, since hash - randomization is enabled by default. + :envvar:`PYTHONHASHSEED` environment variable is set to anything other + than ``random``, since hash randomization is enabled by default. On previous versions of Python, this option turns on hash randomization, so that the :meth:`~object.__hash__` values of str and bytes objects diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h index e13b2b373c47b1..555641943423b4 100644 --- a/Include/cpython/pystate.h +++ b/Include/cpython/pystate.h @@ -28,10 +28,10 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *); #define PyTrace_OPCODE 7 /* Remote debugger support */ -#define Py_MAX_SCRIPT_PATH_SIZE 512 +#define _Py_MAX_SCRIPT_PATH_SIZE 512 typedef struct { int32_t debugger_pending_call; - char debugger_script_path[Py_MAX_SCRIPT_PATH_SIZE]; + char debugger_script_path[_Py_MAX_SCRIPT_PATH_SIZE]; } _PyRemoteDebuggerSupport; typedef struct _err_stackitem { diff --git a/Include/internal/pycore_abstract.h b/Include/internal/pycore_abstract.h index 3cc0afac4bd5b4..30809e097002dd 100644 --- a/Include/internal/pycore_abstract.h +++ b/Include/internal/pycore_abstract.h @@ -51,6 +51,10 @@ extern int _PyObject_RealIsSubclass(PyObject *derived, PyObject *cls); // Export for '_bisect' shared extension. PyAPI_FUNC(int) _Py_convert_optional_to_ssize_t(PyObject *, void *); +// Convert Python int to Py_ssize_t. Do nothing if the argument is None. +// Raises ValueError if argument is negative. +PyAPI_FUNC(int) _Py_convert_optional_to_non_negative_ssize_t(PyObject *, void *); + // Same as PyNumber_Index() but can return an instance of a subclass of int. // Export for 'math' shared extension. PyAPI_FUNC(PyObject*) _PyNumber_Index(PyObject *o); diff --git a/Include/internal/pycore_debug_offsets.h b/Include/internal/pycore_debug_offsets.h index 1b59fa2ef60014..8e7cd16acffa48 100644 --- a/Include/internal/pycore_debug_offsets.h +++ b/Include/internal/pycore_debug_offsets.h @@ -368,7 +368,7 @@ typedef struct _Py_DebugOffsets { .remote_debugging_enabled = offsetof(PyInterpreterState, config.remote_debug), \ .debugger_pending_call = offsetof(_PyRemoteDebuggerSupport, debugger_pending_call), \ .debugger_script_path = offsetof(_PyRemoteDebuggerSupport, debugger_script_path), \ - .debugger_script_path_size = Py_MAX_SCRIPT_PATH_SIZE, \ + .debugger_script_path_size = _Py_MAX_SCRIPT_PATH_SIZE, \ }, \ } diff --git a/Lib/test/clinic.test.c b/Lib/test/clinic.test.c index f28b6a2456942b..ed9058899c0523 100644 --- a/Lib/test/clinic.test.c +++ b/Lib/test/clinic.test.c @@ -1612,12 +1612,17 @@ test_Py_ssize_t_converter a: Py_ssize_t = 12 b: Py_ssize_t(accept={int}) = 34 c: Py_ssize_t(accept={int, NoneType}) = 56 + d: Py_ssize_t(accept={int}, allow_negative=False) = 78 + e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90 + f: Py_ssize_t(accept={int}, allow_negative=True) = -12 + g: Py_ssize_t(accept={int, NoneType}, allow_negative=True) = -34 / [clinic start generated code]*/ PyDoc_STRVAR(test_Py_ssize_t_converter__doc__, -"test_Py_ssize_t_converter($module, a=12, b=34, c=56, /)\n" +"test_Py_ssize_t_converter($module, a=12, b=34, c=56, d=78, e=90, f=-12,\n" +" g=-34, /)\n" "--\n" "\n"); @@ -1626,7 +1631,8 @@ PyDoc_STRVAR(test_Py_ssize_t_converter__doc__, static PyObject * test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b, - Py_ssize_t c); + Py_ssize_t c, Py_ssize_t d, Py_ssize_t e, + Py_ssize_t f, Py_ssize_t g); static PyObject * test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) @@ -1635,8 +1641,12 @@ test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t na Py_ssize_t a = 12; Py_ssize_t b = 34; Py_ssize_t c = 56; + Py_ssize_t d = 78; + Py_ssize_t e = 90; + Py_ssize_t f = -12; + Py_ssize_t g = -34; - if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 3)) { + if (!_PyArg_CheckPositional("test_Py_ssize_t_converter", nargs, 0, 7)) { goto exit; } if (nargs < 1) { @@ -1675,8 +1685,55 @@ test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t na if (!_Py_convert_optional_to_ssize_t(args[2], &c)) { goto exit; } + if (nargs < 4) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[3]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + d = ival; + if (d < 0) { + PyErr_SetString(PyExc_ValueError, + "d cannot be negative"); + goto exit; + } + } + if (nargs < 5) { + goto skip_optional; + } + if (!_Py_convert_optional_to_non_negative_ssize_t(args[4], &e)) { + goto exit; + } + if (nargs < 6) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[5]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + f = ival; + } + if (nargs < 7) { + goto skip_optional; + } + if (!_Py_convert_optional_to_ssize_t(args[6], &g)) { + goto exit; + } skip_optional: - return_value = test_Py_ssize_t_converter_impl(module, a, b, c); + return_value = test_Py_ssize_t_converter_impl(module, a, b, c, d, e, f, g); exit: return return_value; @@ -1684,8 +1741,9 @@ test_Py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t na static PyObject * test_Py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b, - Py_ssize_t c) -/*[clinic end generated code: output=48214bc3d01f4dd7 input=3855f184bb3f299d]*/ + Py_ssize_t c, Py_ssize_t d, Py_ssize_t e, + Py_ssize_t f, Py_ssize_t g) +/*[clinic end generated code: output=4ae0a56a1447fba9 input=a25bac8ecf2890aa]*/ /*[clinic input] diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py index f8d9b0af8f92ec..2cc1aaea0ecbe7 100644 --- a/Lib/test/test_clinic.py +++ b/Lib/test/test_clinic.py @@ -2610,6 +2610,19 @@ def test_disallow_defining_class_at_module_level(self): """ self.expect_failure(block, err, lineno=2) + def test_allow_negative_accepted_by_py_ssize_t_converter_only(self): + errmsg = re.escape("converter_init() got an unexpected keyword argument 'allow_negative'") + unsupported_converters = [converter_name for converter_name in converters.keys() + if converter_name != "Py_ssize_t"] + for converter in unsupported_converters: + with self.subTest(converter=converter): + block = f""" + module m + m.func + a: {converter}(allow_negative=True) + """ + with self.assertRaisesRegex((AssertionError, TypeError), errmsg): + self.parse_function(block) class ClinicExternalTest(TestCase): maxDiff = None @@ -3194,8 +3207,12 @@ def test_py_ssize_t_converter(self): ac_tester.py_ssize_t_converter(PY_SSIZE_T_MAX + 1) with self.assertRaises(TypeError): ac_tester.py_ssize_t_converter([]) - self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56)) - self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None), (1, 2, 56)) + with self.assertRaises(ValueError): + ac_tester.py_ssize_t_converter(12, 34, 56, -1) + with self.assertRaises(ValueError): + ac_tester.py_ssize_t_converter(12, 34, 56, 78, -1) + self.assertEqual(ac_tester.py_ssize_t_converter(), (12, 34, 56, 78, 90, -12, -34)) + self.assertEqual(ac_tester.py_ssize_t_converter(1, 2, None, 3, None, 4, None), (1, 2, 56, 3, 90, 4, -34)) def test_slice_index_converter(self): from _testcapi import PY_SSIZE_T_MIN, PY_SSIZE_T_MAX diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index 41d89e04da8716..c67aaff31b3f5b 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -41,7 +41,7 @@ def writelines(barrier, b, *ignore): def truncate(barrier, b, *ignore): barrier.wait() try: b.truncate(0) - except: BufferError # ignore exported buffer + except BufferError: pass # ignore exported buffer def read(barrier, b, *ignore): barrier.wait() diff --git a/Lib/test/test_opcache.py b/Lib/test/test_opcache.py index 30baa09048616c..f1271fc540ebd8 100644 --- a/Lib/test/test_opcache.py +++ b/Lib/test/test_opcache.py @@ -1333,6 +1333,21 @@ def binary_op_add_int(): self.assert_specialized(binary_op_add_int, "BINARY_OP_ADD_INT") self.assert_no_opcode(binary_op_add_int, "BINARY_OP") + def binary_op_int_non_compact(): + for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): + a, b = 10000000000, 1 + c = a + b + self.assertEqual(c, 10000000001) + c = a - b + self.assertEqual(c, 9999999999) + c = a * b + self.assertEqual(c, 10000000000) + + binary_op_int_non_compact() + self.assert_no_opcode(binary_op_int_non_compact, "BINARY_OP_ADD_INT") + self.assert_no_opcode(binary_op_int_non_compact, "BINARY_OP_SUBTRACT_INT") + self.assert_no_opcode(binary_op_int_non_compact, "BINARY_OP_MULTIPLY_INT") + def binary_op_add_unicode(): for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD): a, b = "foo", "bar" diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-21-52-54.gh-issue-138302.-ez47B.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-21-52-54.gh-issue-138302.-ez47B.rst new file mode 100644 index 00000000000000..156b9df48b40ee --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-09-01-21-52-54.gh-issue-138302.-ez47B.rst @@ -0,0 +1,3 @@ +``BINARY_OP`` now specializes to ``BINARY_OP_ADD_INT``, +``BINARY_OP_SUBTRACT_INT`` or ``BINARY_OP_MULTIPLY_INT`` if operands +are compact ints. diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c index 3e903b6d87d89f..64cefdc7f0b8e4 100644 --- a/Modules/_testclinic.c +++ b/Modules/_testclinic.c @@ -443,16 +443,21 @@ py_ssize_t_converter a: Py_ssize_t = 12 b: Py_ssize_t(accept={int}) = 34 c: Py_ssize_t(accept={int, NoneType}) = 56 + d: Py_ssize_t(accept={int}, allow_negative=False) = 78 + e: Py_ssize_t(accept={int, NoneType}, allow_negative=False) = 90 + f: Py_ssize_t(accept={int}, allow_negative=False) = -12 + g: Py_ssize_t(accept={int, NoneType}, py_default="-34", allow_negative=False) = -34 / [clinic start generated code]*/ static PyObject * py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b, - Py_ssize_t c) -/*[clinic end generated code: output=ce252143e0ed0372 input=76d0f342e9317a1f]*/ + Py_ssize_t c, Py_ssize_t d, Py_ssize_t e, + Py_ssize_t f, Py_ssize_t g) +/*[clinic end generated code: output=ecf8e1a4a9abc95e input=7b7fa954780c1cb0]*/ { - RETURN_PACKED_ARGS(3, PyLong_FromSsize_t, Py_ssize_t, a, b, c); + RETURN_PACKED_ARGS(7, PyLong_FromSsize_t, Py_ssize_t, a, b, c, d, e, f, g); } diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h index 68c92a86226bc9..7e971f7ad7324c 100644 --- a/Modules/clinic/_testclinic.c.h +++ b/Modules/clinic/_testclinic.c.h @@ -1196,7 +1196,8 @@ unsigned_long_long_converter(PyObject *module, PyObject *const *args, Py_ssize_t } PyDoc_STRVAR(py_ssize_t_converter__doc__, -"py_ssize_t_converter($module, a=12, b=34, c=56, /)\n" +"py_ssize_t_converter($module, a=12, b=34, c=56, d=78, e=90, f=-12,\n" +" g=-34, /)\n" "--\n" "\n"); @@ -1205,7 +1206,8 @@ PyDoc_STRVAR(py_ssize_t_converter__doc__, static PyObject * py_ssize_t_converter_impl(PyObject *module, Py_ssize_t a, Py_ssize_t b, - Py_ssize_t c); + Py_ssize_t c, Py_ssize_t d, Py_ssize_t e, + Py_ssize_t f, Py_ssize_t g); static PyObject * py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) @@ -1214,8 +1216,12 @@ py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) Py_ssize_t a = 12; Py_ssize_t b = 34; Py_ssize_t c = 56; + Py_ssize_t d = 78; + Py_ssize_t e = 90; + Py_ssize_t f = -12; + Py_ssize_t g = -34; - if (!_PyArg_CheckPositional("py_ssize_t_converter", nargs, 0, 3)) { + if (!_PyArg_CheckPositional("py_ssize_t_converter", nargs, 0, 7)) { goto exit; } if (nargs < 1) { @@ -1254,8 +1260,60 @@ py_ssize_t_converter(PyObject *module, PyObject *const *args, Py_ssize_t nargs) if (!_Py_convert_optional_to_ssize_t(args[2], &c)) { goto exit; } + if (nargs < 4) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[3]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + d = ival; + if (d < 0) { + PyErr_SetString(PyExc_ValueError, + "d cannot be negative"); + goto exit; + } + } + if (nargs < 5) { + goto skip_optional; + } + if (!_Py_convert_optional_to_non_negative_ssize_t(args[4], &e)) { + goto exit; + } + if (nargs < 6) { + goto skip_optional; + } + { + Py_ssize_t ival = -1; + PyObject *iobj = _PyNumber_Index(args[5]); + if (iobj != NULL) { + ival = PyLong_AsSsize_t(iobj); + Py_DECREF(iobj); + } + if (ival == -1 && PyErr_Occurred()) { + goto exit; + } + f = ival; + if (f < 0) { + PyErr_SetString(PyExc_ValueError, + "f cannot be negative"); + goto exit; + } + } + if (nargs < 7) { + goto skip_optional; + } + if (!_Py_convert_optional_to_non_negative_ssize_t(args[6], &g)) { + goto exit; + } skip_optional: - return_value = py_ssize_t_converter_impl(module, a, b, c); + return_value = py_ssize_t_converter_impl(module, a, b, c, d, e, f, g); exit: return return_value; @@ -4542,4 +4600,4 @@ _testclinic_TestClass_posonly_poskw_varpos_array_no_fastcall(PyObject *type, PyO exit: return return_value; } -/*[clinic end generated code: output=6b04671afdafbecf input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0764e6f8c9d94057 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 27cb8adc55fd4f..a1a1fc794fa563 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -686,7 +686,7 @@ reset_remotedebug_data(PyThreadState *tstate) { tstate->remote_debugger_support.debugger_pending_call = 0; memset(tstate->remote_debugger_support.debugger_script_path, 0, - Py_MAX_SCRIPT_PATH_SIZE); + _Py_MAX_SCRIPT_PATH_SIZE); } diff --git a/Python/modsupport.c b/Python/modsupport.c index 437ad412027e28..17b559e57fa1e7 100644 --- a/Python/modsupport.c +++ b/Python/modsupport.c @@ -33,6 +33,19 @@ _Py_convert_optional_to_ssize_t(PyObject *obj, void *result) return 1; } +int +_Py_convert_optional_to_non_negative_ssize_t(PyObject *obj, void *result) +{ + if (!_Py_convert_optional_to_ssize_t(obj, result)) { + return 0; + } + if (obj != Py_None && *((Py_ssize_t *)result) < 0) { + PyErr_SetString(PyExc_ValueError, "argument cannot be negative"); + return 0; + } + return 1; +} + /* Helper for mkvalue() to scan the length of a format */ diff --git a/Python/specialize.c b/Python/specialize.c index 38df5741f32520..47f7b27b4908fd 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -2595,7 +2595,7 @@ _Py_Specialize_BinaryOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *in specialize(instr, BINARY_OP_ADD_UNICODE); return; } - if (PyLong_CheckExact(lhs)) { + if (_PyLong_CheckExactAndCompact(lhs) && _PyLong_CheckExactAndCompact(rhs)) { specialize(instr, BINARY_OP_ADD_INT); return; } @@ -2609,7 +2609,7 @@ _Py_Specialize_BinaryOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *in if (!Py_IS_TYPE(lhs, Py_TYPE(rhs))) { break; } - if (PyLong_CheckExact(lhs)) { + if (_PyLong_CheckExactAndCompact(lhs) && _PyLong_CheckExactAndCompact(rhs)) { specialize(instr, BINARY_OP_MULTIPLY_INT); return; } @@ -2623,7 +2623,7 @@ _Py_Specialize_BinaryOp(_PyStackRef lhs_st, _PyStackRef rhs_st, _Py_CODEUNIT *in if (!Py_IS_TYPE(lhs, Py_TYPE(rhs))) { break; } - if (PyLong_CheckExact(lhs)) { + if (_PyLong_CheckExactAndCompact(lhs) && _PyLong_CheckExactAndCompact(rhs)) { specialize(instr, BINARY_OP_SUBTRACT_INT); return; } diff --git a/Tools/clinic/libclinic/converters.py b/Tools/clinic/libclinic/converters.py index 6e89e8de7cccf1..d9f93b93d75875 100644 --- a/Tools/clinic/libclinic/converters.py +++ b/Tools/clinic/libclinic/converters.py @@ -420,21 +420,39 @@ class Py_ssize_t_converter(CConverter): type = 'Py_ssize_t' c_ignored_default = "0" - def converter_init(self, *, accept: TypeSet = {int}) -> None: + def converter_init(self, *, accept: TypeSet = {int}, + allow_negative: bool = True) -> None: + self.allow_negative = allow_negative if accept == {int}: self.format_unit = 'n' self.default_type = int elif accept == {int, NoneType}: - self.converter = '_Py_convert_optional_to_ssize_t' + if self.allow_negative: + self.converter = '_Py_convert_optional_to_ssize_t' + else: + self.converter = '_Py_convert_optional_to_non_negative_ssize_t' else: fail(f"Py_ssize_t_converter: illegal 'accept' argument {accept!r}") def use_converter(self) -> None: - if self.converter == '_Py_convert_optional_to_ssize_t': - self.add_include('pycore_abstract.h', - '_Py_convert_optional_to_ssize_t()') + if self.converter in { + '_Py_convert_optional_to_ssize_t', + '_Py_convert_optional_to_non_negative_ssize_t', + }: + self.add_include('pycore_abstract.h', f'{self.converter}()') def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> str | None: + if self.allow_negative: + non_negative_check = '' + else: + non_negative_check = self.format_code(""" + if ({paramname} < 0) {{{{ + PyErr_SetString(PyExc_ValueError, + "{paramname} cannot be negative"); + goto exit; + }}}}""", + argname=argname, + ) if self.format_unit == 'n': if limited_capi: PyNumber_Index = 'PyNumber_Index' @@ -452,11 +470,13 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st if (ival == -1 && PyErr_Occurred()) {{{{ goto exit; }}}} - {paramname} = ival; + {paramname} = ival;{non_negative_check} }}}} """, argname=argname, - PyNumber_Index=PyNumber_Index) + PyNumber_Index=PyNumber_Index, + non_negative_check=non_negative_check, + ) if not limited_capi: return super().parse_arg(argname, displayname, limited_capi=limited_capi) return self.format_code(""" @@ -465,7 +485,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st {paramname} = PyNumber_AsSsize_t({argname}, PyExc_OverflowError); if ({paramname} == -1 && PyErr_Occurred()) {{{{ goto exit; - }}}} + }}}}{non_negative_check} }}}} else {{{{ {bad_argument} @@ -475,6 +495,7 @@ def parse_arg(self, argname: str, displayname: str, *, limited_capi: bool) -> st """, argname=argname, bad_argument=self.bad_argument(displayname, 'integer or None', limited_capi=limited_capi), + non_negative_check=non_negative_check, )