diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index c4a342ee811ca9..ea76315fc09b62 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -828,6 +828,24 @@ PyConfig Default: ``0``. + .. c:member:: int int_max_str_digits + + Configures the :ref:`integer string conversion length limitation + `. 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`` in Python mode. 4300 + (:data:`sys.int_info.default_max_str_digits`) in isolated mode. + + .. 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_capi.py b/Lib/test/test_capi.py index 94f080978b030f..2c6fe34d3b788c 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -999,6 +999,39 @@ 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_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 + + # 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 + """ + before_config = _testinternalcapi.get_config() + assert before_config['int_max_str_digits'] != 55555 + self.assertEqual(support.run_in_subinterp(code), 0, + '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): """ Exceptions saved in global module state get shared between diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 3de8c3d4b11f37..94298003063593 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -882,7 +882,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/Lib/test/test_embed.py b/Lib/test/test_embed.py index 6b5d48547b8acf..c5aeb9459848e4 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': sys.int_info.default_max_str_digits, '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, @@ -912,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) @@ -944,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/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 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. diff --git a/Objects/longobject.c b/Objects/longobject.c index 77a8782d8a675d..d9f3d393b9988a 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); @@ -2578,7 +2578,7 @@ long_from_string_base(const char **str, int base, PyLongObject **res) * quadratic algorithm. */ 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); @@ -6235,10 +6235,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 e5b138ce84bbe8..d635c5a4abe34a 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(); @@ -748,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/initconfig.c b/Python/initconfig.c index bfbb7dbacf9034..bbc2ebb09fd039 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) @@ -849,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 @@ -1021,6 +1020,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 +1128,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 +1318,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 +1422,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; @@ -1782,7 +1791,7 @@ config_init_int_max_str_digits(PyConfig *config) const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS"); if (env) { - int valid = 0; + bool valid = 0; if (!_Py_str_to_int(env, &maxdigits)) { valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); } @@ -1794,13 +1803,13 @@ 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"); if (xoption) { const wchar_t *sep = wcschr(xoption, L'='); - int valid = 0; + bool valid = 0; if (sep) { if (!config_wstr_to_int(sep + 1, &maxdigits)) { valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); @@ -1814,7 +1823,10 @@ 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 < 0) { + config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS; } return _PyStatus_OK(); } @@ -1882,7 +1894,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..584a8be7094bcf 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1717,7 +1717,7 @@ 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] @@ -1734,7 +1734,7 @@ sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits) { PyThreadState *tstate = _PyThreadState_GET(); if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { - tstate->interp->int_max_str_digits = maxdigits; + tstate->interp->config.int_max_str_digits = maxdigits; Py_RETURN_NONE; } else { PyErr_Format( @@ -2810,7 +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(_Py_global_config_int_max_str_digits); + SetFlag(config->int_max_str_digits); #undef SetFlagObj #undef SetFlag return 0; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy