Visitar URL original
gh-96512: Move int_max_str_digits setting to PyConfig (#96944) · python/cpython@b0f89cb · GitHub
Skip to content

Commit b0f89cb

Browse files
authored
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.
1 parent cfbc7dd commit b0f89cb

13 files changed

Lines changed: 111 additions & 24 deletions

File tree

‎Doc/c-api/init_config.rst‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,24 @@ PyConfig
828828
829829
Default: ``0``.
830830
831+
.. c:member:: int int_max_str_digits
832+
833+
Configures the :ref:`integer string conversion length limitation
834+
<int_max_str_digits>`. An initial value of ``-1`` means the value will
835+
be taken from the command line or environment or otherwise default to
836+
4300 (:data:`sys.int_info.default_max_str_digits`). A value of ``0``
837+
disables the limitation. Values greater than zero but less than 640
838+
(:data:`sys.int_info.str_digits_check_threshold`) are unsupported and
839+
will produce an error.
840+
841+
Configured by the :option:`-X int_max_str_digits <-X>` command line
842+
flag or the :envvar:`PYTHONINTMAXSTRDIGITS` environment varable.
843+
844+
Default: ``-1`` in Python mode. 4300
845+
(:data:`sys.int_info.default_max_str_digits`) in isolated mode.
846+
847+
.. versionadded:: 3.12
848+
831849
.. c:member:: int isolated
832850
833851
If greater than ``0``, enable isolated mode:

‎Include/cpython/initconfig.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ typedef struct PyConfig {
178178
wchar_t *check_hash_pycs_mode;
179179
int use_frozen_modules;
180180
int safe_path;
181+
int int_max_str_digits;
181182

182183
/* --- Path configuration inputs ------------ */
183184
int pathconfig_warnings;

‎Include/internal/pycore_initconfig.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ extern void _Py_DumpPathConfig(PyThreadState *tstate);
170170

171171
PyAPI_FUNC(PyObject*) _Py_Get_Getpath_CodeObject(void);
172172

173-
extern int _Py_global_config_int_max_str_digits; // TODO(gpshead): move this into PyConfig in 3.12 after the backports ship.
174-
175173

176174
/* --- Function used for testing ---------------------------------- */
177175

‎Include/internal/pycore_interp.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,6 @@ struct _is {
175175
struct types_state types;
176176
struct callable_cache callable_cache;
177177

178-
int int_max_str_digits;
179-
180178
/* The following fields are here to avoid allocation during init.
181179
The data is exposed through PyInterpreterState pointer fields.
182180
These fields should not be accessed directly outside of init.

‎Lib/test/test_capi.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,39 @@ async def foo(arg): return await arg # Py 3.5
999999
self.assertEqual(ret, 0)
10001000
self.assertEqual(pickle.load(f), {'a': '123x', 'b': '123'})
10011001

1002+
def test_py_config_isoloated_per_interpreter(self):
1003+
# A config change in one interpreter must not leak to out to others.
1004+
#
1005+
# This test could verify ANY config value, it just happens to have been
1006+
# written around the time of int_max_str_digits. Refactoring is okay.
1007+
code = """if 1:
1008+
import sys, _testinternalcapi
1009+
1010+
# Any config value would do, this happens to be the one being
1011+
# double checked at the time this test was written.
1012+
config = _testinternalcapi.get_config()
1013+
config['int_max_str_digits'] = 55555
1014+
_testinternalcapi.set_config(config)
1015+
sub_value = _testinternalcapi.get_config()['int_max_str_digits']
1016+
assert sub_value == 55555, sub_value
1017+
"""
1018+
before_config = _testinternalcapi.get_config()
1019+
assert before_config['int_max_str_digits'] != 55555
1020+
self.assertEqual(support.run_in_subinterp(code), 0,
1021+
'subinterp code failure, check stderr.')
1022+
after_config = _testinternalcapi.get_config()
1023+
self.assertIsNot(
1024+
before_config, after_config,
1025+
"Expected get_config() to return a new dict on each call")
1026+
self.assertEqual(before_config, after_config,
1027+
"CAUTION: Tests executed after this may be "
1028+
"running under an altered config.")
1029+
# try:...finally: calling set_config(before_config) not done
1030+
# as that results in sys.argv, sys.path, and sys.warnoptions
1031+
# "being modified by test_capi" per test.regrtest. So if this
1032+
# test fails, assume that the environment in this process may
1033+
# be altered and suspect.
1034+
10021035
def test_mutate_exception(self):
10031036
"""
10041037
Exceptions saved in global module state get shared between

‎Lib/test/test_cmd_line.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,8 @@ def res2int(res):
882882
return tuple(int(i) for i in out.split())
883883

884884
res = assert_python_ok('-c', code)
885-
self.assertEqual(res2int(res), (-1, sys.get_int_max_str_digits()))
885+
current_max = sys.get_int_max_str_digits()
886+
self.assertEqual(res2int(res), (current_max, current_max))
886887
res = assert_python_ok('-X', 'int_max_str_digits=0', '-c', code)
887888
self.assertEqual(res2int(res), (0, 0))
888889
res = assert_python_ok('-X', 'int_max_str_digits=4000', '-c', code)

‎Lib/test/test_embed.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
434434
'install_signal_handlers': 1,
435435
'use_hash_seed': 0,
436436
'hash_seed': 0,
437+
'int_max_str_digits': sys.int_info.default_max_str_digits,
437438
'faulthandler': 0,
438439
'tracemalloc': 0,
439440
'perf_profiling': 0,
@@ -876,6 +877,7 @@ def test_init_from_config(self):
876877
'platlibdir': 'my_platlibdir',
877878
'module_search_paths': self.IGNORE_CONFIG,
878879
'safe_path': 1,
880+
'int_max_str_digits': 31337,
879881

880882
'check_hash_pycs_mode': 'always',
881883
'pathconfig_warnings': 0,
@@ -912,6 +914,7 @@ def test_init_compat_env(self):
912914
'platlibdir': 'env_platlibdir',
913915
'module_search_paths': self.IGNORE_CONFIG,
914916
'safe_path': 1,
917+
'int_max_str_digits': 4567,
915918
}
916919
self.check_all_configs("test_init_compat_env", config, preconfig,
917920
api=API_COMPAT)
@@ -944,6 +947,7 @@ def test_init_python_env(self):
944947
'platlibdir': 'env_platlibdir',
945948
'module_search_paths': self.IGNORE_CONFIG,
946949
'safe_path': 1,
950+
'int_max_str_digits': 4567,
947951
}
948952
self.check_all_configs("test_init_python_env", config, preconfig,
949953
api=API_PYTHON)

‎Lib/test/test_int.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,26 @@ def test_int_from_other_bases(self):
770770
with self.subTest(base=base):
771771
self._other_base_helper(base)
772772

773+
def test_int_max_str_digits_is_per_interpreter(self):
774+
# Changing the limit in one interpreter does not change others.
775+
code = """if 1:
776+
# Subinterpreters maintain and enforce their own limit
777+
import sys
778+
sys.set_int_max_str_digits(2323)
779+
try:
780+
int('3'*3333)
781+
except ValueError:
782+
pass
783+
else:
784+
raise AssertionError('Expected a int max str digits ValueError.')
785+
"""
786+
with support.adjust_int_max_str_digits(4000):
787+
before_value = sys.get_int_max_str_digits()
788+
self.assertEqual(support.run_in_subinterp(code), 0,
789+
'subinterp code failure, check stderr.')
790+
after_value = sys.get_int_max_str_digits()
791+
self.assertEqual(before_value, after_value)
792+
773793

774794
class IntSubclassStrDigitLimitsTests(IntStrDigitLimitsTests):
775795
int_class = IntSubclass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Configuration for the :ref:`integer string conversion length limitation
2+
<int_max_str_digits>` now lives in the PyConfig C API struct.

‎Objects/longobject.c‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,7 +1767,7 @@ long_to_decimal_string_internal(PyObject *aa,
17671767
if (size_a >= 10 * _PY_LONG_MAX_STR_DIGITS_THRESHOLD
17681768
/ (3 * PyLong_SHIFT) + 2) {
17691769
PyInterpreterState *interp = _PyInterpreterState_GET();
1770-
int max_str_digits = interp->int_max_str_digits;
1770+
int max_str_digits = interp->config.int_max_str_digits;
17711771
if ((max_str_digits > 0) &&
17721772
(max_str_digits / (3 * PyLong_SHIFT) <= (size_a - 11) / 10)) {
17731773
PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_STR,
@@ -1837,7 +1837,7 @@ long_to_decimal_string_internal(PyObject *aa,
18371837
}
18381838
if (strlen > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
18391839
PyInterpreterState *interp = _PyInterpreterState_GET();
1840-
int max_str_digits = interp->int_max_str_digits;
1840+
int max_str_digits = interp->config.int_max_str_digits;
18411841
Py_ssize_t strlen_nosign = strlen - negative;
18421842
if ((max_str_digits > 0) && (strlen_nosign > max_str_digits)) {
18431843
Py_DECREF(scratch);
@@ -2578,7 +2578,7 @@ long_from_string_base(const char **str, int base, PyLongObject **res)
25782578
* quadratic algorithm. */
25792579
if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD) {
25802580
PyInterpreterState *interp = _PyInterpreterState_GET();
2581-
int max_str_digits = interp->int_max_str_digits;
2581+
int max_str_digits = interp->config.int_max_str_digits;
25822582
if ((max_str_digits > 0) && (digits > max_str_digits)) {
25832583
PyErr_Format(PyExc_ValueError, _MAX_STR_DIGITS_ERROR_FMT_TO_INT,
25842584
max_str_digits, digits);
@@ -6235,10 +6235,6 @@ _PyLong_InitTypes(PyInterpreterState *interp)
62356235
return _PyStatus_ERR("can't init int info type");
62366236
}
62376237
}
6238-
interp->int_max_str_digits = _Py_global_config_int_max_str_digits;
6239-
if (interp->int_max_str_digits == -1) {
6240-
interp->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS;
6241-
}
62426238

62436239
return _PyStatus_OK();
62446240
}

0 commit comments

Comments
 (0)