Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-96512: Move int_max_str_digits setting to PyConfig #96944

Merged
merged 9 commits into from
Oct 3, 2022
17 changes: 17 additions & 0 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,23 @@ PyConfig

Default: ``0``.

.. c:member:: int int_max_str_digits

Configures the :ref:`integer string conversion length limitation
<int_max_str_digits>`. ``-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.

gpshead marked this conversation as resolved.
Show resolved Hide resolved
Configured by the :option:`-X int_max_str_digits <-X>` command line
flag or the :envvar:`PYTHONINTMAXSTRDIGITS` environment varable.

Default: ``-1``.
gpshead marked this conversation as resolved.
Show resolved Hide resolved

.. versionadded:: 3.12

.. c:member:: int isolated

If greater than ``0``, enable isolated mode:
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions Include/internal/pycore_initconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---------------------------------- */

Expand Down
2 changes: 0 additions & 2 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
gpshead marked this conversation as resolved.
Show resolved Hide resolved
gpshead marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 3 additions & 7 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
Expand Down
3 changes: 3 additions & 0 deletions Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,9 @@ static int test_init_from_config(void)

config._isolated_interpreter = 1;

putenv("PYTHONINTMAXSTRDIGITS=6666");
gpshead marked this conversation as resolved.
Show resolved Hide resolved
config.int_max_str_digits = 31337;

init_from_config_clear(&config);

dump_config();
Expand Down
8 changes: 4 additions & 4 deletions Python/clinic/sysmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 22 additions & 8 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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) { \
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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");
Expand All @@ -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.");
gpshead marked this conversation as resolved.
Show resolved Hide resolved
}
if (config->int_max_str_digits == -1) {
gpshead marked this conversation as resolved.
Show resolved Hide resolved
config->int_max_str_digits = _PY_LONG_DEFAULT_MAX_STR_DIGITS;
}
return _PyStatus_OK();
}
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 12 additions & 6 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
gpshead marked this conversation as resolved.
Show resolved Hide resolved
maxdigits = INT_MAX;
}
tstate->interp->config.int_max_str_digits = maxdigits;
Py_RETURN_NONE;
} else {
PyErr_Format(
Expand Down Expand Up @@ -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 ?
gpshead marked this conversation as resolved.
Show resolved Hide resolved
-1 : config->int_max_str_digits);
#undef SetFlagObj
#undef SetFlag
return 0;
Expand Down