gh-96512: Move int_max_str_digits setting to PyConfig (#96944)

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.

Adds tests to verify unique settings and configs in subinterpreters.
This commit is contained in:
Gregory P. Smith 2022-10-03 13:55:45 -07:00 committed by GitHub
parent cfbc7dd910
commit b0f89cb431
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 111 additions and 24 deletions

View file

@ -828,6 +828,24 @@ PyConfig
Default: ``0``. Default: ``0``.
.. c:member:: int int_max_str_digits
Configures the :ref:`integer string conversion length limitation
<int_max_str_digits>`. 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 .. c:member:: int isolated
If greater than ``0``, enable isolated mode: If greater than ``0``, enable isolated mode:

View file

@ -178,6 +178,7 @@ typedef struct PyConfig {
wchar_t *check_hash_pycs_mode; wchar_t *check_hash_pycs_mode;
int use_frozen_modules; int use_frozen_modules;
int safe_path; int safe_path;
int int_max_str_digits;
/* --- Path configuration inputs ------------ */ /* --- Path configuration inputs ------------ */
int pathconfig_warnings; int pathconfig_warnings;

View file

@ -170,8 +170,6 @@ extern void _Py_DumpPathConfig(PyThreadState *tstate);
PyAPI_FUNC(PyObject*) _Py_Get_Getpath_CodeObject(void); 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 ---------------------------------- */ /* --- Function used for testing ---------------------------------- */

View file

@ -175,8 +175,6 @@ struct _is {
struct types_state types; struct types_state types;
struct callable_cache callable_cache; struct callable_cache callable_cache;
int int_max_str_digits;
/* The following fields are here to avoid allocation during init. /* The following fields are here to avoid allocation during init.
The data is exposed through PyInterpreterState pointer fields. The data is exposed through PyInterpreterState pointer fields.
These fields should not be accessed directly outside of init. These fields should not be accessed directly outside of init.

View file

@ -999,6 +999,39 @@ class SubinterpreterTest(unittest.TestCase):
self.assertEqual(ret, 0) self.assertEqual(ret, 0)
self.assertEqual(pickle.load(f), {'a': '123x', 'b': '123'}) 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): def test_mutate_exception(self):
""" """
Exceptions saved in global module state get shared between Exceptions saved in global module state get shared between

View file

@ -882,7 +882,8 @@ class CmdLineTest(unittest.TestCase):
return tuple(int(i) for i in out.split()) return tuple(int(i) for i in out.split())
res = assert_python_ok('-c', code) 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) res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code)
self.assertEqual(res2int(res), (0, 0)) self.assertEqual(res2int(res), (0, 0))
res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code) res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code)

View file

@ -434,6 +434,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'install_signal_handlers': 1, 'install_signal_handlers': 1,
'use_hash_seed': 0, 'use_hash_seed': 0,
'hash_seed': 0, 'hash_seed': 0,
'int_max_str_digits': sys.int_info.default_max_str_digits,
'faulthandler': 0, 'faulthandler': 0,
'tracemalloc': 0, 'tracemalloc': 0,
'perf_profiling': 0, 'perf_profiling': 0,
@ -876,6 +877,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'platlibdir': 'my_platlibdir', 'platlibdir': 'my_platlibdir',
'module_search_paths': self.IGNORE_CONFIG, 'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1, 'safe_path': 1,
'int_max_str_digits': 31337,
'check_hash_pycs_mode': 'always', 'check_hash_pycs_mode': 'always',
'pathconfig_warnings': 0, 'pathconfig_warnings': 0,
@ -912,6 +914,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'platlibdir': 'env_platlibdir', 'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG, 'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1, 'safe_path': 1,
'int_max_str_digits': 4567,
} }
self.check_all_configs("test_init_compat_env", config, preconfig, self.check_all_configs("test_init_compat_env", config, preconfig,
api=API_COMPAT) api=API_COMPAT)
@ -944,6 +947,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'platlibdir': 'env_platlibdir', 'platlibdir': 'env_platlibdir',
'module_search_paths': self.IGNORE_CONFIG, 'module_search_paths': self.IGNORE_CONFIG,
'safe_path': 1, 'safe_path': 1,
'int_max_str_digits': 4567,
} }
self.check_all_configs("test_init_python_env", config, preconfig, self.check_all_configs("test_init_python_env", config, preconfig,
api=API_PYTHON) api=API_PYTHON)

View file

@ -770,6 +770,26 @@ class IntStrDigitLimitsTests(unittest.TestCase):
with self.subTest(base=base): with self.subTest(base=base):
self._other_base_helper(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): class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests):
int_class = IntSubclass int_class = IntSubclass

View file

@ -0,0 +1,2 @@
Configuration for the :ref:`integer string conversion length limitation
<int_max_str_digits>` now lives in the PyConfig C API struct.

View file

@ -1767,7 +1767,7 @@ long_to_decimal_string_internal(PyObject *aa,
if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD
/ (3 * PyLong_SHIFT) + 2) { / (3 * PyLong_SHIFT) + 2) {
PyInterpreterState *interp = _PyInterpreterState_GET(); 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) && if ((max_str_digits > 0) &&
(max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) { (max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) {
PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR, 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) { if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
PyInterpreterState *interp = _PyInterpreterState_GET(); 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; Py_ssize_t strlen_nosign = strlen - negative;
if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) { if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) {
Py_DECREF(scratch); Py_DECREF(scratch);
@ -2578,7 +2578,7 @@ long_from_string_base(const char **str, int base, PyLongObject **res)
* quadratic algorithm. */ * quadratic algorithm. */
if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
PyInterpreterState *interp = _PyInterpreterState_GET(); 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)) { if ((max_str_digits > 0) && (digits > max_str_digits)) {
PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT, PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT,
max_str_digits, digits); max_str_digits, digits);
@ -6235,10 +6235,6 @@ _PyLong_InitTypes(PyInterpreterState *interp)
return _PyStatus_ERR("can't init int info type"); 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(); return _PyStatus_OK();
} }

View file

@ -683,6 +683,9 @@ static int test_init_from_config(void)
config._isolated_interpreter = 1; config._isolated_interpreter = 1;
putenv("PYTHONINTMAXSTRDIGITS=6666");
config.int_max_str_digits = 31337;
init_from_config_clear(&config); init_from_config_clear(&config);
dump_config(); dump_config();
@ -748,6 +751,7 @@ static void set_most_env_vars(void)
putenv("PYTHONIOENCODING=iso8859-1:replace"); putenv("PYTHONIOENCODING=iso8859-1:replace");
putenv("PYTHONPLATLIBDIR=env_platlibdir"); putenv("PYTHONPLATLIBDIR=env_platlibdir");
putenv("PYTHONSAFEPATH=1"); putenv("PYTHONSAFEPATH=1");
putenv("PYTHONINTMAXSTRDIGITS=4567");
} }

View file

@ -695,6 +695,7 @@ config_check_consistency(const PyConfig *config)
assert(config->pathconfig_warnings >= 0); assert(config->pathconfig_warnings >= 0);
assert(config->_is_python_build >= 0); assert(config->_is_python_build >= 0);
assert(config->safe_path >= 0); assert(config->safe_path >= 0);
assert(config->int_max_str_digits >= 0);
// config->use_frozen_modules is initialized later // config->use_frozen_modules is initialized later
// by _PyConfig_InitImportConfig(). // by _PyConfig_InitImportConfig().
return 1; return 1;
@ -789,14 +790,11 @@ _PyConfig_InitCompatConfig(PyConfig *config)
config->use_frozen_modules = 1; config->use_frozen_modules = 1;
#endif #endif
config->safe_path = 0; config->safe_path = 0;
config->int_max_str_digits = -1;
config->_is_python_build = 0; config->_is_python_build = 0;
config->code_debug_ranges = 1; 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 static void
config_init_defaults(PyConfig *config) config_init_defaults(PyConfig *config)
@ -849,6 +847,7 @@ PyConfig_InitIsolatedConfig(PyConfig *config)
config->faulthandler = 0; config->faulthandler = 0;
config->tracemalloc = 0; config->tracemalloc = 0;
config->perf_profiling = 0; config->perf_profiling = 0;
config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS;
config->safe_path = 1; config->safe_path = 1;
config->pathconfig_warnings = 0; config->pathconfig_warnings = 0;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
@ -1021,6 +1020,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_ATTR(safe_path); COPY_ATTR(safe_path);
COPY_WSTRLIST(orig_argv); COPY_WSTRLIST(orig_argv);
COPY_ATTR(_is_python_build); COPY_ATTR(_is_python_build);
COPY_ATTR(int_max_str_digits);
#undef COPY_ATTR #undef COPY_ATTR
#undef COPY_WSTR_ATTR #undef COPY_WSTR_ATTR
@ -1128,6 +1128,7 @@ _PyConfig_AsDict(const PyConfig *config)
SET_ITEM_INT(use_frozen_modules); SET_ITEM_INT(use_frozen_modules);
SET_ITEM_INT(safe_path); SET_ITEM_INT(safe_path);
SET_ITEM_INT(_is_python_build); SET_ITEM_INT(_is_python_build);
SET_ITEM_INT(int_max_str_digits);
return dict; return dict;
@ -1317,6 +1318,12 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
} \ } \
CHECK_VALUE(#KEY, config->KEY >= 0); \ CHECK_VALUE(#KEY, config->KEY >= 0); \
} while (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) \ #define GET_WSTR(KEY) \
do { \ do { \
if (config_dict_get_wstr(dict, #KEY, config, &config->KEY) < 0) { \ 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(use_frozen_modules);
GET_UINT(safe_path); GET_UINT(safe_path);
GET_UINT(_is_python_build); GET_UINT(_is_python_build);
GET_INT(int_max_str_digits);
#undef CHECK_VALUE #undef CHECK_VALUE
#undef GET_UINT #undef GET_UINT
#undef GET_INT
#undef GET_WSTR #undef GET_WSTR
#undef GET_WSTR_OPT #undef GET_WSTR_OPT
return 0; return 0;
@ -1782,7 +1791,7 @@ config_init_int_max_str_digits(PyConfig *config)
const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS"); const char *env = config_get_env(config, "PYTHONINTMAXSTRDIGITS");
if (env) { if (env) {
int valid = 0; bool valid = 0;
if (!_Py_str_to_int(env, &maxdigits)) { if (!_Py_str_to_int(env, &maxdigits)) {
valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); 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) STRINGIFY(_PY_LONG_MAX_STR_DIGITS_THRESHOLD)
" or 0 for unlimited."); " 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"); const wchar_t *xoption = config_get_xoption(config, L"int_max_str_digits");
if (xoption) { if (xoption) {
const wchar_t *sep = wcschr(xoption, L'='); const wchar_t *sep = wcschr(xoption, L'=');
int valid = 0; bool valid = 0;
if (sep) { if (sep) {
if (!config_wstr_to_int(sep + 1, &maxdigits)) { if (!config_wstr_to_int(sep + 1, &maxdigits)) {
valid = ((maxdigits == 0) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)); 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
#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(); 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); status = config_init_int_max_str_digits(config);
if (_PyStatus_EXCEPTION(status)) { if (_PyStatus_EXCEPTION(status)) {
return status; return status;

View file

@ -1717,7 +1717,7 @@ sys_get_int_max_str_digits_impl(PyObject *module)
/*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/ /*[clinic end generated code: output=0042f5e8ae0e8631 input=8dab13e2023e60d5]*/
{ {
PyInterpreterState *interp = _PyInterpreterState_GET(); PyInterpreterState *interp = _PyInterpreterState_GET();
return PyLong_FromSsize_t(interp->int_max_str_digits); return PyLong_FromLong(interp->config.int_max_str_digits);
} }
/*[clinic input] /*[clinic input]
@ -1734,7 +1734,7 @@ sys_set_int_max_str_digits_impl(PyObject *module, int maxdigits)
{ {
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
if ((!maxdigits) || (maxdigits >= _PY_LONG_MAX_STR_DIGITS_THRESHOLD)) { 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; Py_RETURN_NONE;
} else { } else {
PyErr_Format( PyErr_Format(
@ -2810,7 +2810,7 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags)
SetFlag(preconfig->utf8_mode); SetFlag(preconfig->utf8_mode);
SetFlag(config->warn_default_encoding); SetFlag(config->warn_default_encoding);
SetFlagObj(PyBool_FromLong(config->safe_path)); SetFlagObj(PyBool_FromLong(config->safe_path));
SetFlag(_Py_global_config_int_max_str_digits); SetFlag(config->int_max_str_digits);
#undef SetFlagObj #undef SetFlagObj
#undef SetFlag #undef SetFlag
return 0; return 0;