From cf20758bbb6f63cbfa6b6f64ee6fab0be48efd9e Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google]" Date: Tue, 20 Sep 2022 00:38:34 +0000 Subject: [PATCH 1/7] gh-96512: Move int_max_str_digits setting to PyConfig It had to live as a global outside of PyConfig for stable ABI reasons in the pre-3.12 backports. This removes the `_Py_global_config_int_max_str_digits` and gets rid of the equivalent field in the internal `struct _is PyInterpreterState` as code can just use the existing nested config struct within that. --- Doc/c-api/init_config.rst | 17 ++++++++++++++++ Include/cpython/initconfig.h | 1 + Include/internal/pycore_initconfig.h | 2 -- Include/internal/pycore_interp.h | 2 -- Lib/test/test_embed.py | 2 ++ Objects/longobject.c | 10 +++------- Programs/_testembed.c | 3 +++ Python/clinic/sysmodule.c.h | 8 ++++---- Python/initconfig.c | 30 ++++++++++++++++++++-------- Python/sysmodule.c | 18 +++++++++++------ 10 files changed, 64 insertions(+), 29 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index c4a342ee811ca9..9860c896cdd091 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -828,6 +828,23 @@ PyConfig Default: ``0``. + .. c:member:: int int_max_str_digits + + Configures the :ref:`integer string conversion length limitation + `. ``-1`` means that + :data:`sys.int_info.default_max_str_digits` will be used. No other + negative value is accepted. ``0`` disables the limitation. Values + greater than zero but less than ``_PY_LONG_MAX_STR_DIGITS_THRESHOLD`` + (640) also known as :data:`sys.int_info.str_digits_check_threshold` + are unsupported and will produce an error. + + Configured by the :option:`-X int_max_str_digits <-X>` command line + flag or the :envvar:`PYTHONINTMAXSTRDIGITS` environment varable. + + Default: ``-1``. + + .. versionadded:: 3.12 + .. c:member:: int isolated If greater than ``0``, enable isolated mode: diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index c6057a4c3ed945..c22c8d52b4f2ec 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -178,6 +178,7 @@ typedef struct PyConfig { wchar_t *check_hash_pycs_mode; int use_frozen_modules; int safe_path; + int int_max_str_digits; /* --- Path configuration inputs ------------ */ int pathconfig_warnings; diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index 6e491261d55ca6..69f88d7d1d46b8 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -170,8 +170,6 @@ extern void _Py_DumpPathConfig(PyThreadState *tstate); PyAPI_FUNC(PyObject*) _Py_Get_Getpath_CodeObject(void); -extern int _Py_global_config_int_max_str_digits; // TODO(gpshead): move this into PyConfig in 3.12 after the backports ship. - /* --- Function used for testing ---------------------------------- */ diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index e7f914ec2fe521..b21708a388b339 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -175,8 +175,6 @@ struct _is { struct types_state types; struct callable_cache callable_cache; - int int_max_str_digits; - /* The following fields are here to avoid allocation during init. The data is exposed through PyInterpreterState pointer fields. These fields should not be accessed directly outside of init. diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 70d7367ea9e64f..82341394cf97a6 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -434,6 +434,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'install_signal_handlers': 1, 'use_hash_seed': 0, 'hash_seed': 0, + 'int_max_str_digits': 4300, 'faulthandler': 0, 'tracemalloc': 0, 'perf_profiling': 0, @@ -876,6 +877,7 @@ def test_init_from_config(self): 'platlibdir': 'my_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, 'safe_path': 1, + 'int_max_str_digits': 31337, 'check_hash_pycs_mode': 'always', 'pathconfig_warnings': 0, diff --git a/Objects/longobject.c b/Objects/longobject.c index c0bade18221843..22d79b0a19d5df 100644 --- a/Objects/longobject.c +++ b/Objects/longobject.c @@ -1767,7 +1767,7 @@ long_to_decimal_string_internal(PyObject *aa, if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD / (3 * PyLong_SHIFT) + 2) { PyInterpreterState *interp = _PyInterpreterState_GET(); - int max_str_digits = interp->int_max_str_digits; + int max_str_digits = interp->config.int_max_str_digits; if ((max_str_digits > 0) && (max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) { PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, @@ -1837,7 +1837,7 @@ long_to_decimal_string_internal(PyObject *aa, } if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { PyInterpreterState *interp = _PyInterpreterState_GET(); - int max_str_digits = interp->int_max_str_digits; + int max_str_digits = interp->config.int_max_str_digits; Py_ssize_t strlen_nosign = strlen - negative; if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) { Py_DECREF(scratch); @@ -2514,7 +2514,7 @@ digit beyond the first. /* Limit the size to avoid excessive computation attacks. */ if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { PyInterpreterState *interp = _PyInterpreterState_GET(); - int max_str_digits = interp->int_max_str_digits; + int max_str_digits = interp->config.int_max_str_digits; if ((max_str_digits > 0) && (digits > max_str_digits)) { PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT, max_str_digits, digits); @@ -6196,10 +6196,6 @@ _PyLong_InitTypes(PyInterpreterState *interp) return _PyStatus_ERR("can't init int info type"); } } - interp->int_max_str_digits = _Py_global_config_int_max_str_digits; - if (interp->int_max_str_digits == -1) { - interp->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; - } return _PyStatus_OK(); } diff --git a/Programs/_testembed.c b/Programs/_testembed.c index a2a2ff40b2774b..e5cad3fc9ae07b 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -683,6 +683,9 @@ static int test_init_from_config(void) config._isolated_interpreter = 1; + putenv("PYTHONINTMAXSTRDIGITS=6666"); + config.int_max_str_digits = 31337; + init_from_config_clear(&config); dump_config(); diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 6864b8b0e03b2f..a789211df9bd2f 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -773,7 +773,7 @@ PyDoc_STRVAR(sys_set_int_max_str_digits__doc__, {"set_int_max_str_digits", _PyCFunction_CAST(sys_set_int_max_str_digits), METH_FASTCALL|METH_KEYWORDS, sys_set_int_max_str_digits__doc__}, static PyObject * -sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits); +sys_set_int_max_str_digits_impl(PyObject *module, long maxdigits); static PyObject * sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -805,13 +805,13 @@ sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t n }; #undef KWTUPLE PyObject *argsbuf[1]; - int maxdigits; + long maxdigits; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); if (!args) { goto exit; } - maxdigits = _PyLong_AsInt(args[0]); + maxdigits = PyLong_AsLong(args[0]); if (maxdigits == -1 && PyErr_Occurred()) { goto exit; } @@ -1343,4 +1343,4 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=15318cdd96b62b06 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=b351d7f0c6fea5be input=a9049054013a1b77]*/ diff --git a/Python/initconfig.c b/Python/initconfig.c index f18ec4068bc443..1d41cb2ad56f7b 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -695,6 +695,7 @@ config_check_consistency(const PyConfig *config) assert(config->pathconfig_warnings >= 0); assert(config->_is_python_build >= 0); assert(config->safe_path >= 0); + assert(config->int_max_str_digits >= 0); // config->use_frozen_modules is initialized later // by _PyConfig_InitImportConfig(). return 1; @@ -789,14 +790,11 @@ _PyConfig_InitCompatConfig(PyConfig *config) config->use_frozen_modules = 1; #endif config->safe_path = 0; + config->int_max_str_digits = -1; config->_is_python_build = 0; config->code_debug_ranges = 1; } -/* Excluded from public struct PyConfig for backporting reasons. */ -/* default to unconfigured, _PyLong_InitTypes() does the rest */ -int _Py_global_config_int_max_str_digits = -1; - static void config_init_defaults(PyConfig *config) @@ -1021,6 +1019,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) COPY_ATTR(safe_path); COPY_WSTRLIST(orig_argv); COPY_ATTR(_is_python_build); + COPY_ATTR(int_max_str_digits); #undef COPY_ATTR #undef COPY_WSTR_ATTR @@ -1128,6 +1127,7 @@ _PyConfig_AsDict(const PyConfig *config) SET_ITEM_INT(use_frozen_modules); SET_ITEM_INT(safe_path); SET_ITEM_INT(_is_python_build); + SET_ITEM_INT(int_max_str_digits); return dict; @@ -1317,6 +1317,12 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) } \ CHECK_VALUE(#KEY, config->KEY >= 0); \ } while (0) +#define GET_INT(KEY) \ + do { \ + if (config_dict_get_int(dict, #KEY, &config->KEY) < 0) { \ + return -1; \ + } \ + } while (0) #define GET_WSTR(KEY) \ do { \ if (config_dict_get_wstr(dict, #KEY, config, &config->KEY) < 0) { \ @@ -1415,9 +1421,11 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) GET_UINT(use_frozen_modules); GET_UINT(safe_path); GET_UINT(_is_python_build); + GET_INT(int_max_str_digits); #undef CHECK_VALUE #undef GET_UINT +#undef GET_INT #undef GET_WSTR #undef GET_WSTR_OPT return 0; @@ -1779,7 +1787,7 @@ static PyStatus config_init_int_max_str_digits(PyConfig *config) { int maxdigits; - int valid = 0; + bool valid = 0; const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS"); if (env) { @@ -1794,7 +1802,7 @@ config_init_int_max_str_digits(PyConfig *config) STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD) " or 0 for unlimited."); } - _Py_global_config_int_max_str_digits = maxdigits; + config->int_max_str_digits = maxdigits; } const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits"); @@ -1813,7 +1821,13 @@ config_init_int_max_str_digits(PyConfig *config) #undef _STRINGIFY #undef STRINGIFY } - _Py_global_config_int_max_str_digits = maxdigits; + config->int_max_str_digits = maxdigits; + } + if (config->int_max_str_digits < -1) { + return _PyStatus_ERR("invalid value: PyConfig.int_max_str_digits < -1."); + } + if (config->int_max_str_digits == -1) { + config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; } return _PyStatus_OK(); } @@ -1881,7 +1895,7 @@ config_read_complex_options(PyConfig *config) } } - if (_Py_global_config_int_max_str_digits < 0) { + if (config->int_max_str_digits < 0) { status = config_init_int_max_str_digits(config); if (_PyStatus_EXCEPTION(status)) { return status; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 653b5a55e885e5..01d9fad414aad2 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1717,24 +1717,29 @@ sys_get_int_max_str_digits_impl(PyObject *module) /*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/ { PyInterpreterState *interp = _PyInterpreterState_GET(); - return PyLong_FromSsize_t(interp->int_max_str_digits); + return PyLong_FromLong(interp->config.int_max_str_digits); } /*[clinic input] sys.set_int_max_str_digits - maxdigits: int + maxdigits: long Set the maximum string digits limit for non-binary int<->str conversions. [clinic start generated code]*/ static PyObject * -sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits) -/*[clinic end generated code: output=734d4c2511f2a56d input=d7e3f325db6910c5]*/ +sys_set_int_max_str_digits_impl(PyObject *module, long maxdigits) +/*[clinic end generated code: output=40ea5d33a82c5c44 input=657c61a1822f6bcf]*/ { PyThreadState *tstate = _PyThreadState_GET(); if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { - tstate->interp->int_max_str_digits = maxdigits; + if (maxdigits > INT_MAX) { + /* Silently cap our range; already effectively unlimited as no + * computation this large can finish. */ + maxdigits = INT_MAX; + } + tstate->interp->config.int_max_str_digits = maxdigits; Py_RETURN_NONE; } else { PyErr_Format( @@ -2810,7 +2815,8 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) SetFlag(preconfig->utf8_mode); SetFlag(config->warn_default_encoding); SetFlagObj(PyBool_FromLong(config->safe_path)); - SetFlag(_Py_global_config_int_max_str_digits); + SetFlag(config->int_max_str_digits == _PY_LONG_DEFAULT_MAX_STR_DIGITS ? + -1 : config->int_max_str_digits); #undef SetFlagObj #undef SetFlag return 0; From 86c4b6e0222fbe0ac8c697fcdf55b388c632da86 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google]" Date: Tue, 20 Sep 2022 00:59:50 +0000 Subject: [PATCH 2/7] Add a test_capi test to verify unique configs. Also demonstrates that subinterpreters maintain their own limit as a feature. --- Lib/test/test_capi.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 94f080978b030f..c4f33349e6812f 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -999,6 +999,30 @@ async def foo(arg): return await arg # Py 3.5 self.assertEqual(ret, 0) self.assertEqual(pickle.load(f), {'a': '123x', 'b': '123'}) + def test_int_max_str_digits_config_is_per_interp(self): + code = """if 1: + import sys, _testinternalcapi + + config = _testinternalcapi.get_config() + config['int_max_str_digits'] = 55555 + _testinternalcapi.set_config(config) + sub_value = _testinternalcapi.get_config()['int_max_str_digits'] + assert sub_value == 55555, sub_value + # Subinterpreters maintain and enforce their own limit + sys.set_int_max_str_digits(2323) + try: + int('3'*3333) + except ValueError: + pass + else: + raise AssertionError('Expected a int max str digits ValueError.') + """ + before_cfg_value = _testinternalcapi.get_config()['int_max_str_digits'] + self.assertEqual(support.run_in_subinterp(code), 0, + 'subinterp code failure, see stderr.') + after_cfg_value = _testinternalcapi.get_config()['int_max_str_digits'] + self.assertEqual(before_cfg_value, after_cfg_value) + def test_mutate_exception(self): """ Exceptions saved in global module state get shared between From 26dfda58ec0764b2372323a80c58da82ad9df57a Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google]" Date: Tue, 20 Sep 2022 01:05:00 +0000 Subject: [PATCH 3/7] NEWS entry. --- .../next/C API/2022-09-20-01-04-57.gh-issue-96512.msZTjF.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2022-09-20-01-04-57.gh-issue-96512.msZTjF.rst diff --git a/Misc/NEWS.d/next/C API/2022-09-20-01-04-57.gh-issue-96512.msZTjF.rst b/Misc/NEWS.d/next/C API/2022-09-20-01-04-57.gh-issue-96512.msZTjF.rst new file mode 100644 index 00000000000000..787bee3ee23b73 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-09-20-01-04-57.gh-issue-96512.msZTjF.rst @@ -0,0 +1,2 @@ +Configuration for the :ref:`integer string conversion length limitation +` now lives in the PyConfig C API struct. From 21a21edc5cb1117e8e82405c2c616ee9965fb20b Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Wed, 21 Sep 2022 16:51:09 -0700 Subject: [PATCH 4/7] Split and move the subinterpreter isolation tests --- Lib/test/test_capi.py | 32 +++++++++++++++++++------------- Lib/test/test_int.py | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index c4f33349e6812f..45096ba502fcb3 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -999,29 +999,35 @@ async def foo(arg): return await arg # Py 3.5 self.assertEqual(ret, 0) self.assertEqual(pickle.load(f), {'a': '123x', 'b': '123'}) - def test_int_max_str_digits_config_is_per_interp(self): + def test_py_config_isoloated_per_interpreter(self): + # A config change in one interpreter must not leak to out to others. code = """if 1: import sys, _testinternalcapi + # Any config value would do, this happens to be the one being + # double checked at the time this test was written. config = _testinternalcapi.get_config() config['int_max_str_digits'] = 55555 _testinternalcapi.set_config(config) sub_value = _testinternalcapi.get_config()['int_max_str_digits'] assert sub_value == 55555, sub_value - # Subinterpreters maintain and enforce their own limit - sys.set_int_max_str_digits(2323) - try: - int('3'*3333) - except ValueError: - pass - else: - raise AssertionError('Expected a int max str digits ValueError.') """ - before_cfg_value = _testinternalcapi.get_config()['int_max_str_digits'] + before_config = _testinternalcapi.get_config() + assert before_config['int_max_str_digits'] != 55555 self.assertEqual(support.run_in_subinterp(code), 0, - 'subinterp code failure, see stderr.') - after_cfg_value = _testinternalcapi.get_config()['int_max_str_digits'] - self.assertEqual(before_cfg_value, after_cfg_value) + 'subinterp code failure, check stderr.') + after_config = _testinternalcapi.get_config() + self.assertIsNot( + before_config, after_config, + "Expected get_config() to return a new dict on each call") + self.assertEqual(before_config, after_config, + "CAUTION: Tests executed after this may be " + "running under an altered config.") + # try:...finally: calling set_config(before_config) not done + # as that results in sys.argv, sys.path, and sys.warnoptions + # "being modified by test_capi" per test.regrtest. So if this + # test fails, assume that the environment in this process may + # be altered and suspect. def test_mutate_exception(self): """ diff --git a/Lib/test/test_int.py b/Lib/test/test_int.py index c972b8afb48d75..625c388cd947b9 100644 --- a/Lib/test/test_int.py +++ b/Lib/test/test_int.py @@ -770,6 +770,26 @@ def test_int_from_other_bases(self): with self.subTest(base=base): self._other_base_helper(base) + def test_int_max_str_digits_is_per_interpreter(self): + # Changing the limit in one interpreter does not change others. + code = """if 1: + # Subinterpreters maintain and enforce their own limit + import sys + sys.set_int_max_str_digits(2323) + try: + int('3'*3333) + except ValueError: + pass + else: + raise AssertionError('Expected a int max str digits ValueError.') + """ + with support.adjust_int_max_str_digits(4000): + before_value = sys.get_int_max_str_digits() + self.assertEqual(support.run_in_subinterp(code), 0, + 'subinterp code failure, check stderr.') + after_value = sys.get_int_max_str_digits() + self.assertEqual(before_value, after_value) + class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests): int_class = IntSubclass From 8b4561dbe582ebcc6e85ac9429f10decaeb7ed73 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 22 Sep 2022 17:39:16 -0700 Subject: [PATCH 5/7] Address review comments. --- Doc/c-api/init_config.rst | 15 ++++++++------- Lib/test/test_embed.py | 4 +++- Programs/_testembed.c | 1 + Python/clinic/sysmodule.c.h | 8 ++++---- Python/initconfig.c | 6 ++---- Python/sysmodule.c | 11 +++-------- 6 files changed, 21 insertions(+), 24 deletions(-) diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 9860c896cdd091..ea76315fc09b62 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -831,17 +831,18 @@ PyConfig .. c:member:: int int_max_str_digits Configures the :ref:`integer string conversion length limitation - `. ``-1`` means that - :data:`sys.int_info.default_max_str_digits` will be used. No other - negative value is accepted. ``0`` disables the limitation. Values - greater than zero but less than ``_PY_LONG_MAX_STR_DIGITS_THRESHOLD`` - (640) also known as :data:`sys.int_info.str_digits_check_threshold` - are unsupported and will produce an error. + `. An initial value of ``-1`` means the value will + be taken from the command line or environment or otherwise default to + 4300 (:data:`sys.int_info.default_max_str_digits`). A value of ``0`` + disables the limitation. Values greater than zero but less than 640 + (:data:`sys.int_info.str_digits_check_threshold`) are unsupported and + will produce an error. Configured by the :option:`-X int_max_str_digits <-X>` command line flag or the :envvar:`PYTHONINTMAXSTRDIGITS` environment varable. - Default: ``-1``. + Default: ``-1`` in Python mode. 4300 + (:data:`sys.int_info.default_max_str_digits`) in isolated mode. .. versionadded:: 3.12 diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 82341394cf97a6..d2103c700bf252 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -434,7 +434,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'install_signal_handlers': 1, 'use_hash_seed': 0, 'hash_seed': 0, - 'int_max_str_digits': 4300, + 'int_max_str_digits': sys.int_info.default_max_str_digits, 'faulthandler': 0, 'tracemalloc': 0, 'perf_profiling': 0, @@ -914,6 +914,7 @@ def test_init_compat_env(self): 'platlibdir': 'env_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, 'safe_path': 1, + 'int_max_str_digits': 4567, } self.check_all_configs("test_init_compat_env", config, preconfig, api=API_COMPAT) @@ -946,6 +947,7 @@ def test_init_python_env(self): 'platlibdir': 'env_platlibdir', 'module_search_paths': self.IGNORE_CONFIG, 'safe_path': 1, + 'int_max_str_digits': 4567, } self.check_all_configs("test_init_python_env", config, preconfig, api=API_PYTHON) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index e5cad3fc9ae07b..5d687f1dd30dc3 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -751,6 +751,7 @@ static void set_most_env_vars(void) putenv("PYTHONIOENCODING=iso8859-1:replace"); putenv("PYTHONPLATLIBDIR=env_platlibdir"); putenv("PYTHONSAFEPATH=1"); + putenv("PYTHONINTMAXSTRDIGITS=4567"); } diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index a789211df9bd2f..6864b8b0e03b2f 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -773,7 +773,7 @@ PyDoc_STRVAR(sys_set_int_max_str_digits__doc__, {"set_int_max_str_digits", _PyCFunction_CAST(sys_set_int_max_str_digits), METH_FASTCALL|METH_KEYWORDS, sys_set_int_max_str_digits__doc__}, static PyObject * -sys_set_int_max_str_digits_impl(PyObject *module, long maxdigits); +sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits); static PyObject * sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -805,13 +805,13 @@ sys_set_int_max_str_digits(PyObject *module, PyObject *const *args, Py_ssize_t n }; #undef KWTUPLE PyObject *argsbuf[1]; - long maxdigits; + int maxdigits; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); if (!args) { goto exit; } - maxdigits = PyLong_AsLong(args[0]); + maxdigits = _PyLong_AsInt(args[0]); if (maxdigits == -1 && PyErr_Occurred()) { goto exit; } @@ -1343,4 +1343,4 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=b351d7f0c6fea5be input=a9049054013a1b77]*/ +/*[clinic end generated code: output=15318cdd96b62b06 input=a9049054013a1b77]*/ diff --git a/Python/initconfig.c b/Python/initconfig.c index 1d41cb2ad56f7b..a3dfc2824542c6 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -847,6 +847,7 @@ PyConfig_InitIsolatedConfig(PyConfig *config) config->faulthandler = 0; config->tracemalloc = 0; config->perf_profiling = 0; + config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; config->safe_path = 1; config->pathconfig_warnings = 0; #ifdef MS_WINDOWS @@ -1823,10 +1824,7 @@ config_init_int_max_str_digits(PyConfig *config) } config->int_max_str_digits = maxdigits; } - if (config->int_max_str_digits < -1) { - return _PyStatus_ERR("invalid value: PyConfig.int_max_str_digits < -1."); - } - if (config->int_max_str_digits == -1) { + if (config->int_max_str_digits < 0) { config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; } return _PyStatus_OK(); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 01d9fad414aad2..e1273813265bd7 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1723,22 +1723,17 @@ sys_get_int_max_str_digits_impl(PyObject *module) /*[clinic input] sys.set_int_max_str_digits - maxdigits: long + maxdigits: int Set the maximum string digits limit for non-binary int<->str conversions. [clinic start generated code]*/ static PyObject * -sys_set_int_max_str_digits_impl(PyObject *module, long maxdigits) -/*[clinic end generated code: output=40ea5d33a82c5c44 input=657c61a1822f6bcf]*/ +sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits) +/*[clinic end generated code: output=734d4c2511f2a56d input=d7e3f325db6910c5]*/ { PyThreadState *tstate = _PyThreadState_GET(); if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { - if (maxdigits > INT_MAX) { - /* Silently cap our range; already effectively unlimited as no - * computation this large can finish. */ - maxdigits = INT_MAX; - } tstate->interp->config.int_max_str_digits = maxdigits; Py_RETURN_NONE; } else { From 7247e780fe74b346f99bf8afe3721bf9e83b1d4d Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Thu, 22 Sep 2022 17:46:54 -0700 Subject: [PATCH 6/7] Don't expose -1 in sys.flags. --- Lib/test/test_cmd_line.py | 3 ++- Python/sysmodule.c | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index db967088804ae2..e1b8472f6fab6a 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -880,7 +880,8 @@ def res2int(res): return tuple(int(i) for i in out.split()) res = assert_python_ok('-c', code) - self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits())) + current_max = sys.get_int_max_str_digits() + self.assertEqual(res2int(res), (current_max, current_max)) res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code) self.assertEqual(res2int(res), (0, 0)) res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e1273813265bd7..584a8be7094bcf 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2810,8 +2810,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) SetFlag(preconfig->utf8_mode); SetFlag(config->warn_default_encoding); SetFlagObj(PyBool_FromLong(config->safe_path)); - SetFlag(config->int_max_str_digits == _PY_LONG_DEFAULT_MAX_STR_DIGITS ? - -1 : config->int_max_str_digits); + SetFlag(config->int_max_str_digits); #undef SetFlagObj #undef SetFlag return 0; From 1a5935dcecec7cf3a3d90c3193dd228af489c4a9 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Mon, 3 Oct 2022 11:59:35 -0700 Subject: [PATCH 7/7] clarify the purpose of the test_capi test via comment --- Lib/test/test_capi.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 45096ba502fcb3..2c6fe34d3b788c 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -1001,6 +1001,9 @@ async def foo(arg): return await arg # Py 3.5 def test_py_config_isoloated_per_interpreter(self): # A config change in one interpreter must not leak to out to others. + # + # This test could verify ANY config value, it just happens to have been + # written around the time of int_max_str_digits. Refactoring is okay. code = """if 1: import sys, _testinternalcapi