From 33b790978d8b817a66a4a117a8c38a857b6103f0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 2 Sep 2024 23:25:08 +0200 Subject: [PATCH] gh-107954, PEP 741: Add PyConfig_Get()/Set() functions (#123472) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PyConfig_Get(), PyConfig_GetInt(), PyConfig_Set() and PyConfig_Names() functions to get and set the current runtime Python configuration. Add visibility and "sys spec" to config and preconfig specifications. _PyConfig_AsDict() now converts PyConfig.xoptions as a dictionary. Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/c-api/init_config.rst | 69 ++ Doc/whatsnew/3.14.rst | 9 + Include/cpython/initconfig.h | 8 + Include/internal/pycore_initconfig.h | 2 +- Include/internal/pycore_sysmodule.h | 3 + Lib/test/_test_embed_set_config.py | 23 +- Lib/test/test_capi/test_config.py | 379 ++++++++ Lib/test/test_embed.py | 104 +- ...-08-29-15-55-55.gh-issue-107954.pr2O50.rst | 8 + Modules/Setup.stdlib.in | 2 +- Modules/_testcapi/config.c | 68 ++ Modules/_testcapi/parts.h | 1 + Modules/_testcapimodule.c | 3 + PCbuild/_testcapi.vcxproj | 1 + PCbuild/_testcapi.vcxproj.filters | 3 + Python/initconfig.c | 898 +++++++++++++++--- Python/sysmodule.c | 144 +-- Tools/c-analyzer/cpython/_parser.py | 1 + Tools/c-analyzer/cpython/ignored.tsv | 1 + 19 files changed, 1468 insertions(+), 259 deletions(-) create mode 100644 Lib/test/test_capi/test_config.py create mode 100644 Misc/NEWS.d/next/C_API/2024-08-29-15-55-55.gh-issue-107954.pr2O50.rst create mode 100644 Modules/_testcapi/config.c diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 6e2e04fba55..355b8e010e0 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -1605,6 +1605,75 @@ customized Python always running in isolated mode using :c:func:`Py_RunMain`. +Runtime Python configuration API +================================ + +The configuration option *name* parameter must be a non-NULL null-terminated +UTF-8 encoded string. + +Some options are read from the :mod:`sys` attributes. For example, the option +``"argv"`` is read from :data:`sys.argv`. + + +.. c:function:: PyObject* PyConfig_Get(const char *name) + + Get the current runtime value of a configuration option as a Python object. + + * Return a new reference on success. + * Set an exception and return ``NULL`` on error. + + The object type depends on the configuration option. It can be: + + * ``bool`` + * ``int`` + * ``str`` + * ``list[str]`` + * ``dict[str, str]`` + + The caller must hold the GIL. The function cannot be called before + Python initialization nor after Python finalization. + + .. versionadded:: 3.14 + + +.. c:function:: int PyConfig_GetInt(const char *name, int *value) + + Similar to :c:func:`PyConfig_Get`, but get the value as a C int. + + * Return ``0`` on success. + * Set an exception and return ``-1`` on error. + + .. versionadded:: 3.14 + + +.. c:function:: PyObject* PyConfig_Names(void) + + Get all configuration option names as a ``frozenset``. + + * Return a new reference on success. + * Set an exception and return ``NULL`` on error. + + The caller must hold the GIL. The function cannot be called before + Python initialization nor after Python finalization. + + .. versionadded:: 3.14 + + +.. c:function:: int PyConfig_Set(const char *name, PyObject *value) + + Set the current runtime value of a configuration option. + + * Raise a :exc:`ValueError` if there is no option *name*. + * Raise a :exc:`ValueError` if *value* is an invalid value. + * Raise a :exc:`ValueError` if the option is read-only (cannot be set). + * Raise a :exc:`TypeError` if *value* has not the proper type. + + The caller must hold the GIL. The function cannot be called before + Python initialization nor after Python finalization. + + .. versionadded:: 3.14 + + Py_GetArgcArgv() ================ diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2ab4102f32a..4817c525805 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -492,6 +492,15 @@ New Features * Add :c:func:`Py_HashBuffer` to compute and return the hash value of a buffer. (Contributed by Antoine Pitrou and Victor Stinner in :gh:`122854`.) +* Add functions to get and set the current runtime Python configuration: + + * :c:func:`PyConfig_Get` + * :c:func:`PyConfig_GetInt` + * :c:func:`PyConfig_Set` + * :c:func:`PyConfig_Names` + + (Contributed by Victor Stinner in :gh:`107954`.) + Porting to Python 3.14 ---------------------- diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 5da5ef9e543..83faab7e270 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -260,6 +260,14 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, Py_ssize_t length, wchar_t **items); +/* --- PyConfig_Get() ----------------------------------------- */ + +PyAPI_FUNC(PyObject*) PyConfig_Get(const char *name); +PyAPI_FUNC(int) PyConfig_GetInt(const char *name, int *value); +PyAPI_FUNC(PyObject*) PyConfig_Names(void); +PyAPI_FUNC(int) PyConfig_Set(const char *name, PyObject *value); + + /* --- Helper functions --------------------------------------- */ /* Get the original command line arguments, before Python modified them. diff --git a/Include/internal/pycore_initconfig.h b/Include/internal/pycore_initconfig.h index 6bf1b53bffd..25ec8cec3bd 100644 --- a/Include/internal/pycore_initconfig.h +++ b/Include/internal/pycore_initconfig.h @@ -181,7 +181,7 @@ extern PyStatus _PyConfig_Write(const PyConfig *config, extern PyStatus _PyConfig_SetPyArgv( PyConfig *config, const _PyArgv *args); - +extern PyObject* _PyConfig_CreateXOptionsDict(const PyConfig *config); extern void _Py_DumpPathConfig(PyThreadState *tstate); diff --git a/Include/internal/pycore_sysmodule.h b/Include/internal/pycore_sysmodule.h index 9b8eafd3d6c..a1d795e284f 100644 --- a/Include/internal/pycore_sysmodule.h +++ b/Include/internal/pycore_sysmodule.h @@ -29,6 +29,9 @@ extern int _PySys_SetAttr(PyObject *, PyObject *); extern int _PySys_ClearAttrString(PyInterpreterState *interp, const char *name, int verbose); +extern int _PySys_SetFlagObj(Py_ssize_t pos, PyObject *new_value); +extern int _PySys_SetIntMaxStrDigits(int maxdigits); + #ifdef __cplusplus } #endif diff --git a/Lib/test/_test_embed_set_config.py b/Lib/test/_test_embed_set_config.py index 23423d5b7a5..7edb35da463 100644 --- a/Lib/test/_test_embed_set_config.py +++ b/Lib/test/_test_embed_set_config.py @@ -137,7 +137,8 @@ class SetConfigTests(unittest.TestCase): 'warnoptions', 'module_search_paths', ): - value_tests.append((key, invalid_wstrlist)) + if key != 'xoptions': + value_tests.append((key, invalid_wstrlist)) type_tests.append((key, 123)) type_tests.append((key, "abc")) type_tests.append((key, [123])) @@ -160,14 +161,14 @@ class SetConfigTests(unittest.TestCase): def test_flags(self): bool_options = set(BOOL_OPTIONS) for sys_attr, key, value in ( - ("debug", "parser_debug", 1), - ("inspect", "inspect", 2), - ("interactive", "interactive", 3), - ("optimize", "optimization_level", 4), - ("verbose", "verbose", 1), - ("bytes_warning", "bytes_warning", 10), - ("quiet", "quiet", 11), - ("isolated", "isolated", 12), + ("debug", "parser_debug", 2), + ("inspect", "inspect", 3), + ("interactive", "interactive", 4), + ("optimize", "optimization_level", 5), + ("verbose", "verbose", 6), + ("bytes_warning", "bytes_warning", 7), + ("quiet", "quiet", 8), + ("isolated", "isolated", 9), ): with self.subTest(sys=sys_attr, key=key, value=value): self.set_config(**{key: value, 'parse_argv': 0}) @@ -228,9 +229,9 @@ class SetConfigTests(unittest.TestCase): self.check(warnoptions=[]) self.check(warnoptions=["default", "ignore"]) - self.set_config(xoptions=[]) + self.set_config(xoptions={}) self.assertEqual(sys._xoptions, {}) - self.set_config(xoptions=["dev", "tracemalloc=5"]) + self.set_config(xoptions={"dev": True, "tracemalloc": "5"}) self.assertEqual(sys._xoptions, {"dev": True, "tracemalloc": "5"}) def test_pathconfig(self): diff --git a/Lib/test/test_capi/test_config.py b/Lib/test/test_capi/test_config.py new file mode 100644 index 00000000000..01637e1cb7b --- /dev/null +++ b/Lib/test/test_capi/test_config.py @@ -0,0 +1,379 @@ +""" +Tests PyConfig_Get() and PyConfig_Set() C API (PEP 741). +""" +import os +import sys +import sysconfig +import types +import unittest +from test import support +from test.support import import_helper + +_testcapi = import_helper.import_module('_testcapi') + + +# Is the Py_STATS macro defined? +Py_STATS = hasattr(sys, '_stats_on') + + +class CAPITests(unittest.TestCase): + def test_config_get(self): + # Test PyConfig_Get() + config_get = _testcapi.config_get + config_names = _testcapi.config_names + + TEST_VALUE = { + str: "TEST_MARKER_STR", + str | None: "TEST_MARKER_OPT_STR", + list[str]: ("TEST_MARKER_STR_TUPLE",), + dict[str, str | bool]: {"x": "value", "y": True}, + } + + # read config options and check their type + options = [ + ("allocator", int, None), + ("argv", list[str], "argv"), + ("base_exec_prefix", str | None, "base_exec_prefix"), + ("base_executable", str | None, "_base_executable"), + ("base_prefix", str | None, "base_prefix"), + ("buffered_stdio", bool, None), + ("bytes_warning", int, None), + ("check_hash_pycs_mode", str, None), + ("code_debug_ranges", bool, None), + ("configure_c_stdio", bool, None), + ("coerce_c_locale", bool, None), + ("coerce_c_locale_warn", bool, None), + ("configure_locale", bool, None), + ("cpu_count", int, None), + ("dev_mode", bool, None), + ("dump_refs", bool, None), + ("dump_refs_file", str | None, None), + ("exec_prefix", str | None, "exec_prefix"), + ("executable", str | None, "executable"), + ("faulthandler", bool, None), + ("filesystem_encoding", str, None), + ("filesystem_errors", str, None), + ("hash_seed", int, None), + ("home", str | None, None), + ("import_time", bool, None), + ("inspect", bool, None), + ("install_signal_handlers", bool, None), + ("int_max_str_digits", int, None), + ("interactive", bool, None), + ("isolated", bool, None), + ("malloc_stats", bool, None), + ("module_search_paths", list[str], "path"), + ("optimization_level", int, None), + ("orig_argv", list[str], "orig_argv"), + ("parser_debug", bool, None), + ("parse_argv", bool, None), + ("pathconfig_warnings", bool, None), + ("perf_profiling", bool, None), + ("platlibdir", str, "platlibdir"), + ("prefix", str | None, "prefix"), + ("program_name", str, None), + ("pycache_prefix", str | None, "pycache_prefix"), + ("quiet", bool, None), + ("run_command", str | None, None), + ("run_filename", str | None, None), + ("run_module", str | None, None), + ("safe_path", bool, None), + ("show_ref_count", bool, None), + ("site_import", bool, None), + ("skip_source_first_line", bool, None), + ("stdio_encoding", str, None), + ("stdio_errors", str, None), + ("stdlib_dir", str | None, "_stdlib_dir"), + ("tracemalloc", int, None), + ("use_environment", bool, None), + ("use_frozen_modules", bool, None), + ("use_hash_seed", bool, None), + ("user_site_directory", bool, None), + ("utf8_mode", bool, None), + ("verbose", int, None), + ("warn_default_encoding", bool, None), + ("warnoptions", list[str], "warnoptions"), + ("write_bytecode", bool, None), + ("xoptions", dict[str, str | bool], "_xoptions"), + ] + if support.Py_DEBUG: + options.append(("run_presite", str | None, None)) + if sysconfig.get_config_var('Py_GIL_DISABLED'): + options.append(("enable_gil", int, None)) + if support.MS_WINDOWS: + options.extend(( + ("legacy_windows_stdio", bool, None), + ("legacy_windows_fs_encoding", bool, None), + )) + if Py_STATS: + options.extend(( + ("_pystats", bool, None), + )) + + for name, option_type, sys_attr in options: + with self.subTest(name=name, option_type=option_type, + sys_attr=sys_attr): + value = config_get(name) + if isinstance(option_type, types.GenericAlias): + self.assertIsInstance(value, option_type.__origin__) + if option_type.__origin__ == dict: + key_type = option_type.__args__[0] + value_type = option_type.__args__[1] + for item in value.items(): + self.assertIsInstance(item[0], key_type) + self.assertIsInstance(item[1], value_type) + else: + item_type = option_type.__args__[0] + for item in value: + self.assertIsInstance(item, item_type) + else: + self.assertIsInstance(value, option_type) + + if sys_attr is not None: + expected = getattr(sys, sys_attr) + self.assertEqual(expected, value) + + override = TEST_VALUE[option_type] + with support.swap_attr(sys, sys_attr, override): + self.assertEqual(config_get(name), override) + + # check that the test checks all options + self.assertEqual(sorted(name for name, option_type, sys_attr in options), + sorted(config_names())) + + def test_config_get_sys_flags(self): + # Test PyConfig_Get() + config_get = _testcapi.config_get + + # compare config options with sys.flags + for flag, name, negate in ( + ("debug", "parser_debug", False), + ("inspect", "inspect", False), + ("interactive", "interactive", False), + ("optimize", "optimization_level", False), + ("dont_write_bytecode", "write_bytecode", True), + ("no_user_site", "user_site_directory", True), + ("no_site", "site_import", True), + ("ignore_environment", "use_environment", True), + ("verbose", "verbose", False), + ("bytes_warning", "bytes_warning", False), + ("quiet", "quiet", False), + # "hash_randomization" is tested below + ("isolated", "isolated", False), + ("dev_mode", "dev_mode", False), + ("utf8_mode", "utf8_mode", False), + ("warn_default_encoding", "warn_default_encoding", False), + ("safe_path", "safe_path", False), + ("int_max_str_digits", "int_max_str_digits", False), + # "gil" is tested below + ): + with self.subTest(flag=flag, name=name, negate=negate): + value = config_get(name) + if negate: + value = not value + self.assertEqual(getattr(sys.flags, flag), value) + + self.assertEqual(sys.flags.hash_randomization, + config_get('use_hash_seed') == 0 + or config_get('hash_seed') != 0) + + if sysconfig.get_config_var('Py_GIL_DISABLED'): + value = config_get('enable_gil') + expected = (value if value != -1 else None) + self.assertEqual(sys.flags.gil, expected) + + def test_config_get_non_existent(self): + # Test PyConfig_Get() on non-existent option name + config_get = _testcapi.config_get + nonexistent_key = 'NONEXISTENT_KEY' + err_msg = f'unknown config option name: {nonexistent_key}' + with self.assertRaisesRegex(ValueError, err_msg): + config_get(nonexistent_key) + + def test_config_get_write_bytecode(self): + # PyConfig_Get("write_bytecode") gets sys.dont_write_bytecode + # as an integer + config_get = _testcapi.config_get + with support.swap_attr(sys, "dont_write_bytecode", 0): + self.assertEqual(config_get('write_bytecode'), 1) + with support.swap_attr(sys, "dont_write_bytecode", "yes"): + self.assertEqual(config_get('write_bytecode'), 0) + with support.swap_attr(sys, "dont_write_bytecode", []): + self.assertEqual(config_get('write_bytecode'), 1) + + def test_config_getint(self): + # Test PyConfig_GetInt() + config_getint = _testcapi.config_getint + + # PyConfig_MEMBER_INT type + self.assertEqual(config_getint('verbose'), sys.flags.verbose) + + # PyConfig_MEMBER_UINT type + self.assertEqual(config_getint('isolated'), sys.flags.isolated) + + # PyConfig_MEMBER_ULONG type + self.assertIsInstance(config_getint('hash_seed'), int) + + # PyPreConfig member + self.assertIsInstance(config_getint('allocator'), int) + + # platlibdir type is str + with self.assertRaises(TypeError): + config_getint('platlibdir') + + def test_get_config_names(self): + names = _testcapi.config_names() + self.assertIsInstance(names, frozenset) + for name in names: + self.assertIsInstance(name, str) + + def test_config_set_sys_attr(self): + # Test PyConfig_Set() with sys attributes + config_get = _testcapi.config_get + config_set = _testcapi.config_set + + # mutable configuration option mapped to sys attributes + for name, sys_attr, option_type in ( + ('argv', 'argv', list[str]), + ('base_exec_prefix', 'base_exec_prefix', str | None), + ('base_executable', '_base_executable', str | None), + ('base_prefix', 'base_prefix', str | None), + ('exec_prefix', 'exec_prefix', str | None), + ('executable', 'executable', str | None), + ('module_search_paths', 'path', list[str]), + ('platlibdir', 'platlibdir', str), + ('prefix', 'prefix', str | None), + ('pycache_prefix', 'pycache_prefix', str | None), + ('stdlib_dir', '_stdlib_dir', str | None), + ('warnoptions', 'warnoptions', list[str]), + ('xoptions', '_xoptions', dict[str, str | bool]), + ): + with self.subTest(name=name): + if option_type == str: + test_values = ('TEST_REPLACE',) + invalid_types = (1, None) + elif option_type == str | None: + test_values = ('TEST_REPLACE', None) + invalid_types = (123,) + elif option_type == list[str]: + test_values = (['TEST_REPLACE'], []) + invalid_types = ('text', 123, [123]) + else: # option_type == dict[str, str | bool]: + test_values = ({"x": "value", "y": True},) + invalid_types = ('text', 123, ['option'], + {123: 'value'}, + {'key': b'bytes'}) + + old_opt_value = config_get(name) + old_sys_value = getattr(sys, sys_attr) + try: + for value in test_values: + config_set(name, value) + self.assertEqual(config_get(name), value) + self.assertEqual(getattr(sys, sys_attr), value) + + for value in invalid_types: + with self.assertRaises(TypeError): + config_set(name, value) + finally: + setattr(sys, sys_attr, old_sys_value) + config_set(name, old_opt_value) + + def test_config_set_sys_flag(self): + # Test PyConfig_Set() with sys.flags + config_get = _testcapi.config_get + config_set = _testcapi.config_set + + # mutable configuration option mapped to sys.flags + class unsigned_int(int): + pass + + def expect_int(value): + value = int(value) + return (value, value) + + def expect_bool(value): + value = int(bool(value)) + return (value, value) + + def expect_bool_not(value): + value = bool(value) + return (int(value), int(not value)) + + for name, sys_flag, option_type, expect_func in ( + # (some flags cannot be set, see comments below.) + ('parser_debug', 'debug', bool, expect_bool), + ('inspect', 'inspect', bool, expect_bool), + ('interactive', 'interactive', bool, expect_bool), + ('optimization_level', 'optimize', unsigned_int, expect_int), + ('write_bytecode', 'dont_write_bytecode', bool, expect_bool_not), + # user_site_directory + # site_import + ('use_environment', 'ignore_environment', bool, expect_bool_not), + ('verbose', 'verbose', unsigned_int, expect_int), + ('bytes_warning', 'bytes_warning', unsigned_int, expect_int), + ('quiet', 'quiet', bool, expect_bool), + # hash_randomization + # isolated + # dev_mode + # utf8_mode + # warn_default_encoding + # safe_path + ('int_max_str_digits', 'int_max_str_digits', unsigned_int, expect_int), + # gil + ): + if name == "int_max_str_digits": + new_values = (0, 5_000, 999_999) + invalid_values = (-1, 40) # value must 0 or >= 4300 + invalid_types = (1.0, "abc") + elif option_type == int: + new_values = (False, True, 0, 1, 5, -5) + invalid_values = () + invalid_types = (1.0, "abc") + else: + new_values = (False, True, 0, 1, 5) + invalid_values = (-5,) + invalid_types = (1.0, "abc") + + with self.subTest(name=name): + old_value = config_get(name) + try: + for value in new_values: + expected, expect_flag = expect_func(value) + + config_set(name, value) + self.assertEqual(config_get(name), expected) + self.assertEqual(getattr(sys.flags, sys_flag), expect_flag) + if name == "write_bytecode": + self.assertEqual(getattr(sys, "dont_write_bytecode"), + expect_flag) + if name == "int_max_str_digits": + self.assertEqual(sys.get_int_max_str_digits(), + expect_flag) + + for value in invalid_values: + with self.assertRaises(ValueError): + config_set(name, value) + + for value in invalid_types: + with self.assertRaises(TypeError): + config_set(name, value) + finally: + config_set(name, old_value) + + def test_config_set_read_only(self): + # Test PyConfig_Set() on read-only options + config_set = _testcapi.config_set + for name, value in ( + ("allocator", 0), # PyPreConfig member + ("cpu_count", 8), + ("dev_mode", True), + ("filesystem_encoding", "utf-8"), + ): + with self.subTest(name=name, value=value): + with self.assertRaisesRegex(ValueError, r"read-only"): + config_set(name, value) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 7860c67f082..b1d1f6d06d7 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -508,30 +508,30 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): PRE_CONFIG_COMPAT = { '_config_init': API_COMPAT, 'allocator': PYMEM_ALLOCATOR_NOT_SET, - 'parse_argv': 0, - 'configure_locale': 1, - 'coerce_c_locale': 0, - 'coerce_c_locale_warn': 0, - 'utf8_mode': 0, + 'parse_argv': False, + 'configure_locale': True, + 'coerce_c_locale': False, + 'coerce_c_locale_warn': False, + 'utf8_mode': False, } if MS_WINDOWS: PRE_CONFIG_COMPAT.update({ - 'legacy_windows_fs_encoding': 0, + 'legacy_windows_fs_encoding': False, }) PRE_CONFIG_PYTHON = dict(PRE_CONFIG_COMPAT, _config_init=API_PYTHON, - parse_argv=1, + parse_argv=True, coerce_c_locale=GET_DEFAULT_CONFIG, utf8_mode=GET_DEFAULT_CONFIG, ) PRE_CONFIG_ISOLATED = dict(PRE_CONFIG_COMPAT, _config_init=API_ISOLATED, - configure_locale=0, - isolated=1, - use_environment=0, - utf8_mode=0, - dev_mode=0, - coerce_c_locale=0, + configure_locale=False, + isolated=True, + use_environment=False, + utf8_mode=False, + dev_mode=False, + coerce_c_locale=False, ) COPY_PRE_CONFIG = [ @@ -570,7 +570,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'argv': [""], 'orig_argv': [], - 'xoptions': [], + 'xoptions': {}, 'warnoptions': [], 'pythonpath_env': None, @@ -619,14 +619,14 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): '_is_python_build': IGNORE_CONFIG, } if Py_STATS: - CONFIG_COMPAT['_pystats'] = 0 + CONFIG_COMPAT['_pystats'] = False if support.Py_DEBUG: CONFIG_COMPAT['run_presite'] = None if support.Py_GIL_DISABLED: CONFIG_COMPAT['enable_gil'] = -1 if MS_WINDOWS: CONFIG_COMPAT.update({ - 'legacy_windows_stdio': 0, + 'legacy_windows_stdio': False, }) CONFIG_PYTHON = dict(CONFIG_COMPAT, @@ -644,12 +644,12 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): install_signal_handlers=False, use_hash_seed=False, faulthandler=False, - tracemalloc=0, + tracemalloc=False, perf_profiling=False, pathconfig_warnings=False, ) if MS_WINDOWS: - CONFIG_ISOLATED['legacy_windows_stdio'] = 0 + CONFIG_ISOLATED['legacy_windows_stdio'] = False # global config DEFAULT_GLOBAL_CONFIG = { @@ -928,23 +928,23 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): def test_init_global_config(self): preconfig = { - 'utf8_mode': 1, + 'utf8_mode': True, } config = { 'program_name': './globalvar', - 'site_import': 0, - 'bytes_warning': 1, + 'site_import': False, + 'bytes_warning': True, 'warnoptions': ['default::BytesWarning'], - 'inspect': 1, - 'interactive': 1, + 'inspect': True, + 'interactive': True, 'optimization_level': 2, - 'write_bytecode': 0, - 'verbose': 1, - 'quiet': 1, - 'buffered_stdio': 0, + 'write_bytecode': False, + 'verbose': True, + 'quiet': True, + 'buffered_stdio': False, - 'user_site_directory': 0, - 'pathconfig_warnings': 0, + 'user_site_directory': False, + 'pathconfig_warnings': False, } self.check_all_configs("test_init_global_config", config, preconfig, api=API_COMPAT) @@ -952,7 +952,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): def test_init_from_config(self): preconfig = { 'allocator': ALLOCATOR_FOR_CONFIG, - 'utf8_mode': 1, + 'utf8_mode': True, } config = { 'install_signal_handlers': False, @@ -977,12 +977,12 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): '-c', 'pass', 'arg2'], 'parse_argv': True, - 'xoptions': [ - 'config_xoption1=3', - 'config_xoption2=', - 'config_xoption3', - 'cmdline_xoption', - ], + 'xoptions': { + 'config_xoption1': '3', + 'config_xoption2': '', + 'config_xoption3': True, + 'cmdline_xoption': True, + }, 'warnoptions': [ 'cmdline_warnoption', 'default::BytesWarning', @@ -1126,7 +1126,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'dev_mode': True, 'faulthandler': True, 'warnoptions': ['default'], - 'xoptions': ['dev'], + 'xoptions': {'dev': True}, 'safe_path': True, } self.check_all_configs("test_preinit_parse_argv", config, preconfig, @@ -1135,7 +1135,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): def test_preinit_dont_parse_argv(self): # -X dev must be ignored by isolated preconfiguration preconfig = { - 'isolated': 0, + 'isolated': False, } argv = ["python3", "-E", "-I", "-P", @@ -1145,7 +1145,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config = { 'argv': argv, 'orig_argv': argv, - 'isolated': 0, + 'isolated': False, } self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig, api=API_ISOLATED) @@ -1218,12 +1218,12 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): def test_init_sys_add(self): config = { 'faulthandler': 1, - 'xoptions': [ - 'config_xoption', - 'cmdline_xoption', - 'sysadd_xoption', - 'faulthandler', - ], + 'xoptions': { + 'config_xoption': True, + 'cmdline_xoption': True, + 'sysadd_xoption': True, + 'faulthandler': True, + }, 'warnoptions': [ 'ignore:::cmdline_warnoption', 'ignore:::sysadd_warnoption', @@ -1259,7 +1259,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'program_name': './python3', 'run_command': code + '\n', 'parse_argv': True, - '_init_main': 0, + '_init_main': False, 'sys_path_0': '', } self.check_all_configs("test_init_main", config, @@ -1637,12 +1637,12 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config['base_prefix'] = pyvenv_home config['prefix'] = pyvenv_home config['stdlib_dir'] = os.path.join(pyvenv_home, 'Lib') - config['use_frozen_modules'] = int(not support.Py_DEBUG) + config['use_frozen_modules'] = bool(not support.Py_DEBUG) else: # cannot reliably assume stdlib_dir here because it # depends too much on our build. But it ought to be found config['stdlib_dir'] = self.IGNORE_CONFIG - config['use_frozen_modules'] = int(not support.Py_DEBUG) + config['use_frozen_modules'] = bool(not support.Py_DEBUG) env = self.copy_paths_by_env(config) self.check_all_configs("test_init_compat_config", config, @@ -1706,7 +1706,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config = _testinternalcapi.get_configs()['config'] - self.assertEqual(Py_GetPath().split(os.path.pathsep), + self.assertEqual(tuple(Py_GetPath().split(os.path.pathsep)), config['module_search_paths']) self.assertEqual(Py_GetPrefix(), config['prefix']) self.assertEqual(Py_GetExecPrefix(), config['exec_prefix']) @@ -1763,6 +1763,10 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): } for raw, expected in tests: optval = f'frozen_modules{raw}' + if raw.startswith('='): + xoption_value = raw[1:] + else: + xoption_value = True config = { 'parse_argv': True, 'argv': ['-c'], @@ -1770,7 +1774,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'program_name': './argv0', 'run_command': 'pass\n', 'use_environment': True, - 'xoptions': [optval], + 'xoptions': {'frozen_modules': xoption_value}, 'use_frozen_modules': expected, } env = {'TESTFROZEN': raw[1:]} if raw else None diff --git a/Misc/NEWS.d/next/C_API/2024-08-29-15-55-55.gh-issue-107954.pr2O50.rst b/Misc/NEWS.d/next/C_API/2024-08-29-15-55-55.gh-issue-107954.pr2O50.rst new file mode 100644 index 00000000000..f1116870c9d --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-08-29-15-55-55.gh-issue-107954.pr2O50.rst @@ -0,0 +1,8 @@ +Add functions to get and set the current runtime Python configuration: + +* :c:func:`PyConfig_Get` +* :c:func:`PyConfig_GetInt` +* :c:func:`PyConfig_Set` +* :c:func:`PyConfig_Names` + +Patch by Victor Stinner. diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 9121a8c5dc6..9aa398a80ef 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -162,7 +162,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/monitoring.c _testcapi/config.c @MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/sys.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c @MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c @MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c diff --git a/Modules/_testcapi/config.c b/Modules/_testcapi/config.c new file mode 100644 index 00000000000..bb3b7e88953 --- /dev/null +++ b/Modules/_testcapi/config.c @@ -0,0 +1,68 @@ +#include "parts.h" + + +static PyObject * +_testcapi_config_get(PyObject *module, PyObject *name_obj) +{ + const char *name; + if (PyArg_Parse(name_obj, "s", &name) < 0) { + return NULL; + } + + return PyConfig_Get(name); +} + + +static PyObject * +_testcapi_config_getint(PyObject *module, PyObject *name_obj) +{ + const char *name; + if (PyArg_Parse(name_obj, "s", &name) < 0) { + return NULL; + } + + int value; + if (PyConfig_GetInt(name, &value) < 0) { + return NULL; + } + return PyLong_FromLong(value); +} + + +static PyObject * +_testcapi_config_names(PyObject *module, PyObject* Py_UNUSED(args)) +{ + return PyConfig_Names(); +} + + +static PyObject * +_testcapi_config_set(PyObject *module, PyObject *args) +{ + const char *name; + PyObject *value; + if (PyArg_ParseTuple(args, "sO", &name, &value) < 0) { + return NULL; + } + + int res = PyConfig_Set(name, value); + if (res < 0) { + return NULL; + } + Py_RETURN_NONE; +} + + +static PyMethodDef test_methods[] = { + {"config_get", _testcapi_config_get, METH_O}, + {"config_getint", _testcapi_config_getint, METH_O}, + {"config_names", _testcapi_config_names, METH_NOARGS}, + {"config_set", _testcapi_config_set, METH_VARARGS}, + {NULL} +}; + +int +_PyTestCapi_Init_Config(PyObject *mod) +{ + return PyModule_AddFunctions(mod, test_methods); +} diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index 41d190961c6..65ba77596c7 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -60,5 +60,6 @@ int _PyTestCapi_Init_Hash(PyObject *module); int _PyTestCapi_Init_Time(PyObject *module); int _PyTestCapi_Init_Monitoring(PyObject *module); int _PyTestCapi_Init_Object(PyObject *module); +int _PyTestCapi_Init_Config(PyObject *mod); #endif // Py_TESTCAPI_PARTS_H diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 7ffa87dcd10..5966eb674cf 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4172,6 +4172,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Object(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Config(m) < 0) { + return NULL; + } PyState_AddModule(m, &_testcapimodule); return m; diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 44dbf234813..c41235eac35 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -126,6 +126,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index cae44bc955f..0a00df655de 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -111,6 +111,9 @@ Source Files + + Source Files + diff --git a/Python/initconfig.c b/Python/initconfig.c index 51897a2d0ae..1c21486d8f2 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -10,6 +10,7 @@ #include "pycore_pymem.h" // _PyMem_SetDefaultAllocator() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_pystats.h" // _Py_StatsOn() +#include "pycore_sysmodule.h" // _PySys_SetIntMaxStrDigits() #include "osdefs.h" // DELIM @@ -26,6 +27,22 @@ #include "config_common.h" +/* --- PyConfig setters ------------------------------------------- */ + +typedef PyObject* (*config_sys_flag_setter) (int value); + +static PyObject* +config_sys_flag_long(int value) +{ + return PyLong_FromLong(value); +} + +static PyObject* +config_sys_flag_not(int value) +{ + value = (!value); + return config_sys_flag_long(value); +} /* --- PyConfig spec ---------------------------------------------- */ @@ -40,99 +57,174 @@ typedef enum { PyConfig_MEMBER_WSTR_LIST = 12, } PyConfigMemberType; +typedef enum { + // Option which cannot be get or set by PyConfig_Get() and PyConfig_Set() + PyConfig_MEMBER_INIT_ONLY = 0, + + // Option which cannot be set by PyConfig_Set() + PyConfig_MEMBER_READ_ONLY = 1, + + // Public option: can be get and set by PyConfig_Get() and PyConfig_Set() + PyConfig_MEMBER_PUBLIC = 2, +} PyConfigMemberVisibility; + +typedef struct { + const char *attr; + int flag_index; + config_sys_flag_setter flag_setter; +} PyConfigSysSpec; + typedef struct { const char *name; size_t offset; PyConfigMemberType type; + PyConfigMemberVisibility visibility; + PyConfigSysSpec sys; } PyConfigSpec; -#define SPEC(MEMBER, TYPE) \ - {#MEMBER, offsetof(PyConfig, MEMBER), PyConfig_MEMBER_##TYPE} +#define SPEC(MEMBER, TYPE, VISIBILITY, sys) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + PyConfig_MEMBER_##TYPE, PyConfig_MEMBER_##VISIBILITY, sys} + +#define SYS_ATTR(name) {name, -1, NULL} +#define SYS_FLAG_SETTER(index, setter) {NULL, index, setter} +#define SYS_FLAG(index) SYS_FLAG_SETTER(index, NULL) +#define NO_SYS SYS_ATTR(NULL) // Update _test_embed_set_config when adding new members static const PyConfigSpec PYCONFIG_SPEC[] = { - SPEC(_config_init, UINT), - SPEC(isolated, BOOL), - SPEC(use_environment, BOOL), - SPEC(dev_mode, BOOL), - SPEC(install_signal_handlers, BOOL), - SPEC(use_hash_seed, BOOL), - SPEC(hash_seed, ULONG), - SPEC(faulthandler, BOOL), - SPEC(tracemalloc, UINT), - SPEC(perf_profiling, UINT), - SPEC(import_time, BOOL), - SPEC(code_debug_ranges, BOOL), - SPEC(show_ref_count, BOOL), - SPEC(dump_refs, BOOL), - SPEC(dump_refs_file, WSTR_OPT), - SPEC(malloc_stats, BOOL), - SPEC(filesystem_encoding, WSTR), - SPEC(filesystem_errors, WSTR), - SPEC(pycache_prefix, WSTR_OPT), - SPEC(parse_argv, BOOL), - SPEC(orig_argv, WSTR_LIST), - SPEC(argv, WSTR_LIST), - SPEC(xoptions, WSTR_LIST), - SPEC(warnoptions, WSTR_LIST), - SPEC(site_import, BOOL), - SPEC(bytes_warning, UINT), - SPEC(warn_default_encoding, BOOL), - SPEC(inspect, BOOL), - SPEC(interactive, BOOL), - SPEC(optimization_level, UINT), - SPEC(parser_debug, BOOL), - SPEC(write_bytecode, BOOL), - SPEC(verbose, UINT), - SPEC(quiet, BOOL), - SPEC(user_site_directory, BOOL), - SPEC(configure_c_stdio, BOOL), - SPEC(buffered_stdio, BOOL), - SPEC(stdio_encoding, WSTR), - SPEC(stdio_errors, WSTR), -#ifdef MS_WINDOWS - SPEC(legacy_windows_stdio, BOOL), -#endif - SPEC(check_hash_pycs_mode, WSTR), - SPEC(use_frozen_modules, BOOL), - SPEC(safe_path, BOOL), - SPEC(int_max_str_digits, INT), - SPEC(cpu_count, INT), -#ifdef Py_GIL_DISABLED - SPEC(enable_gil, INT), -#endif - SPEC(pathconfig_warnings, BOOL), - SPEC(program_name, WSTR), - SPEC(pythonpath_env, WSTR_OPT), - SPEC(home, WSTR_OPT), - SPEC(platlibdir, WSTR), - SPEC(sys_path_0, WSTR_OPT), - SPEC(module_search_paths_set, BOOL), - SPEC(module_search_paths, WSTR_LIST), - SPEC(stdlib_dir, WSTR_OPT), - SPEC(executable, WSTR_OPT), - SPEC(base_executable, WSTR_OPT), - SPEC(prefix, WSTR_OPT), - SPEC(base_prefix, WSTR_OPT), - SPEC(exec_prefix, WSTR_OPT), - SPEC(base_exec_prefix, WSTR_OPT), - SPEC(skip_source_first_line, BOOL), - SPEC(run_command, WSTR_OPT), - SPEC(run_module, WSTR_OPT), - SPEC(run_filename, WSTR_OPT), - SPEC(_install_importlib, BOOL), - SPEC(_init_main, BOOL), - SPEC(_is_python_build, BOOL), + // --- Public options ----------- + + SPEC(argv, WSTR_LIST, PUBLIC, SYS_ATTR("argv")), + SPEC(base_exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_exec_prefix")), + SPEC(base_executable, WSTR_OPT, PUBLIC, SYS_ATTR("_base_executable")), + SPEC(base_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("base_prefix")), + SPEC(bytes_warning, UINT, PUBLIC, SYS_FLAG(9)), + SPEC(exec_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("exec_prefix")), + SPEC(executable, WSTR_OPT, PUBLIC, SYS_ATTR("executable")), + SPEC(inspect, BOOL, PUBLIC, SYS_FLAG(1)), + SPEC(int_max_str_digits, UINT, PUBLIC, NO_SYS), + SPEC(interactive, BOOL, PUBLIC, SYS_FLAG(2)), + SPEC(module_search_paths, WSTR_LIST, PUBLIC, SYS_ATTR("path")), + SPEC(optimization_level, UINT, PUBLIC, SYS_FLAG(3)), + SPEC(parser_debug, BOOL, PUBLIC, SYS_FLAG(0)), + SPEC(platlibdir, WSTR, PUBLIC, SYS_ATTR("platlibdir")), + SPEC(prefix, WSTR_OPT, PUBLIC, SYS_ATTR("prefix")), + SPEC(pycache_prefix, WSTR_OPT, PUBLIC, SYS_ATTR("pycache_prefix")), + SPEC(quiet, BOOL, PUBLIC, SYS_FLAG(10)), + SPEC(stdlib_dir, WSTR_OPT, PUBLIC, SYS_ATTR("_stdlib_dir")), + SPEC(use_environment, BOOL, PUBLIC, SYS_FLAG_SETTER(7, config_sys_flag_not)), + SPEC(verbose, UINT, PUBLIC, SYS_FLAG(8)), + SPEC(warnoptions, WSTR_LIST, PUBLIC, SYS_ATTR("warnoptions")), + SPEC(write_bytecode, BOOL, PUBLIC, SYS_FLAG_SETTER(4, config_sys_flag_not)), + SPEC(xoptions, WSTR_LIST, PUBLIC, SYS_ATTR("_xoptions")), + + // --- Read-only options ----------- + #ifdef Py_STATS - SPEC(_pystats, BOOL), + SPEC(_pystats, BOOL, READ_ONLY, NO_SYS), #endif + SPEC(buffered_stdio, BOOL, READ_ONLY, NO_SYS), + SPEC(check_hash_pycs_mode, WSTR, READ_ONLY, NO_SYS), + SPEC(code_debug_ranges, BOOL, READ_ONLY, NO_SYS), + SPEC(configure_c_stdio, BOOL, READ_ONLY, NO_SYS), + SPEC(cpu_count, INT, READ_ONLY, NO_SYS), + SPEC(dev_mode, BOOL, READ_ONLY, NO_SYS), // sys.flags.dev_mode + SPEC(dump_refs, BOOL, READ_ONLY, NO_SYS), + SPEC(dump_refs_file, WSTR_OPT, READ_ONLY, NO_SYS), +#ifdef Py_GIL_DISABLED + SPEC(enable_gil, INT, READ_ONLY, NO_SYS), +#endif + SPEC(faulthandler, BOOL, READ_ONLY, NO_SYS), + SPEC(filesystem_encoding, WSTR, READ_ONLY, NO_SYS), + SPEC(filesystem_errors, WSTR, READ_ONLY, NO_SYS), + SPEC(hash_seed, ULONG, READ_ONLY, NO_SYS), + SPEC(home, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(import_time, BOOL, READ_ONLY, NO_SYS), + SPEC(install_signal_handlers, BOOL, READ_ONLY, NO_SYS), + SPEC(isolated, BOOL, READ_ONLY, NO_SYS), // sys.flags.isolated +#ifdef MS_WINDOWS + SPEC(legacy_windows_stdio, BOOL, READ_ONLY, NO_SYS), +#endif + SPEC(malloc_stats, BOOL, READ_ONLY, NO_SYS), + SPEC(orig_argv, WSTR_LIST, READ_ONLY, SYS_ATTR("orig_argv")), + SPEC(parse_argv, BOOL, READ_ONLY, NO_SYS), + SPEC(pathconfig_warnings, BOOL, READ_ONLY, NO_SYS), + SPEC(perf_profiling, BOOL, READ_ONLY, NO_SYS), + SPEC(program_name, WSTR, READ_ONLY, NO_SYS), + SPEC(run_command, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(run_filename, WSTR_OPT, READ_ONLY, NO_SYS), + SPEC(run_module, WSTR_OPT, READ_ONLY, NO_SYS), #ifdef Py_DEBUG - SPEC(run_presite, WSTR_OPT), + SPEC(run_presite, WSTR_OPT, READ_ONLY, NO_SYS), #endif - {NULL, 0, 0}, + SPEC(safe_path, BOOL, READ_ONLY, NO_SYS), + SPEC(show_ref_count, BOOL, READ_ONLY, NO_SYS), + SPEC(site_import, BOOL, READ_ONLY, NO_SYS), // sys.flags.no_site + SPEC(skip_source_first_line, BOOL, READ_ONLY, NO_SYS), + SPEC(stdio_encoding, WSTR, READ_ONLY, NO_SYS), + SPEC(stdio_errors, WSTR, READ_ONLY, NO_SYS), + SPEC(tracemalloc, UINT, READ_ONLY, NO_SYS), + SPEC(use_frozen_modules, BOOL, READ_ONLY, NO_SYS), + SPEC(use_hash_seed, BOOL, READ_ONLY, NO_SYS), + SPEC(user_site_directory, BOOL, READ_ONLY, NO_SYS), // sys.flags.no_user_site + SPEC(warn_default_encoding, BOOL, READ_ONLY, NO_SYS), + + // --- Init-only options ----------- + + SPEC(_config_init, UINT, INIT_ONLY, NO_SYS), + SPEC(_init_main, BOOL, INIT_ONLY, NO_SYS), + SPEC(_install_importlib, BOOL, INIT_ONLY, NO_SYS), + SPEC(_is_python_build, BOOL, INIT_ONLY, NO_SYS), + SPEC(module_search_paths_set, BOOL, INIT_ONLY, NO_SYS), + SPEC(pythonpath_env, WSTR_OPT, INIT_ONLY, NO_SYS), + SPEC(sys_path_0, WSTR_OPT, INIT_ONLY, NO_SYS), + + // Array terminator + {NULL, 0, 0, 0, NO_SYS}, }; #undef SPEC +#define SPEC(MEMBER, TYPE, VISIBILITY) \ + {#MEMBER, offsetof(PyPreConfig, MEMBER), PyConfig_MEMBER_##TYPE, \ + PyConfig_MEMBER_##VISIBILITY, NO_SYS} + +static const PyConfigSpec PYPRECONFIG_SPEC[] = { + // --- Read-only options ----------- + + SPEC(allocator, INT, READ_ONLY), + SPEC(coerce_c_locale, BOOL, READ_ONLY), + SPEC(coerce_c_locale_warn, BOOL, READ_ONLY), + SPEC(configure_locale, BOOL, READ_ONLY), +#ifdef MS_WINDOWS + SPEC(legacy_windows_fs_encoding, BOOL, READ_ONLY), +#endif + SPEC(utf8_mode, BOOL, READ_ONLY), + + // --- Init-only options ----------- + // Members already present in PYCONFIG_SPEC + + SPEC(_config_init, INT, INIT_ONLY), + SPEC(dev_mode, BOOL, INIT_ONLY), + SPEC(isolated, BOOL, INIT_ONLY), + SPEC(parse_argv, BOOL, INIT_ONLY), + SPEC(use_environment, BOOL, INIT_ONLY), + + // Array terminator + {NULL, 0, 0, 0, NO_SYS}, +}; + +#undef SPEC +#undef SYS_ATTR +#undef SYS_FLAG_SETTER +#undef SYS_FLAG +#undef NO_SYS + + +// Forward declarations +static PyObject* +config_get(const PyConfig *config, const PyConfigSpec *spec, + int use_sys); /* --- Command line options --------------------------------------- */ @@ -656,6 +748,28 @@ _PyWideStringList_AsList(const PyWideStringList *list) } +static PyObject* +_PyWideStringList_AsTuple(const PyWideStringList *list) +{ + assert(_PyWideStringList_CheckConsistency(list)); + + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; +} + + /* --- Py_GetArgcArgv() ------------------------------------------- */ void @@ -1059,54 +1173,12 @@ _PyConfig_AsDict(const PyConfig *config) const PyConfigSpec *spec = PYCONFIG_SPEC; for (; spec->name != NULL; spec++) { - char *member = (char *)config + spec->offset; - PyObject *obj; - switch (spec->type) { - case PyConfig_MEMBER_INT: - case PyConfig_MEMBER_UINT: - { - int value = *(int*)member; - obj = PyLong_FromLong(value); - break; - } - case PyConfig_MEMBER_BOOL: - { - int value = *(int*)member; - obj = PyBool_FromLong(value); - break; - } - case PyConfig_MEMBER_ULONG: - { - unsigned long value = *(unsigned long*)member; - obj = PyLong_FromUnsignedLong(value); - break; - } - case PyConfig_MEMBER_WSTR: - case PyConfig_MEMBER_WSTR_OPT: - { - const wchar_t *wstr = *(const wchar_t**)member; - if (wstr != NULL) { - obj = PyUnicode_FromWideChar(wstr, -1); - } - else { - obj = Py_NewRef(Py_None); - } - break; - } - case PyConfig_MEMBER_WSTR_LIST: - { - const PyWideStringList *list = (const PyWideStringList*)member; - obj = _PyWideStringList_AsList(list); - break; - } - default: - Py_UNREACHABLE(); - } - + PyObject *obj = config_get(config, spec, 0); if (obj == NULL) { Py_DECREF(dict); return NULL; } + int res = PyDict_SetItemString(dict, spec->name, obj); Py_DECREF(obj); if (res < 0) { @@ -1218,15 +1290,17 @@ config_dict_get_wstrlist(PyObject *dict, const char *name, PyConfig *config, return -1; } - if (!PyList_CheckExact(list)) { + int is_list = PyList_CheckExact(list); + if (!is_list && !PyTuple_CheckExact(list)) { Py_DECREF(list); config_dict_invalid_type(name); return -1; } PyWideStringList wstrlist = _PyWideStringList_INIT; - for (Py_ssize_t i=0; i < PyList_GET_SIZE(list); i++) { - PyObject *item = PyList_GET_ITEM(list, i); + Py_ssize_t len = is_list ? PyList_GET_SIZE(list) : PyTuple_GET_SIZE(list); + for (Py_ssize_t i=0; i < len; i++) { + PyObject *item = is_list ? PyList_GET_ITEM(list, i) : PyTuple_GET_ITEM(list, i); if (item == Py_None) { config_dict_invalid_value(name); @@ -1263,6 +1337,66 @@ error: } +static int +config_dict_get_xoptions(PyObject *dict, const char *name, PyConfig *config, + PyWideStringList *result) +{ + PyObject *xoptions = config_dict_get(dict, name); + if (xoptions == NULL) { + return -1; + } + + if (!PyDict_CheckExact(xoptions)) { + Py_DECREF(xoptions); + config_dict_invalid_type(name); + return -1; + } + + Py_ssize_t pos = 0; + PyObject *key, *value; + PyWideStringList wstrlist = _PyWideStringList_INIT; + while (PyDict_Next(xoptions, &pos, &key, &value)) { + PyObject *item; + + if (value != Py_True) { + item = PyUnicode_FromFormat("%S=%S", key, value); + if (item == NULL) { + goto error; + } + } + else { + item = Py_NewRef(key); + } + + wchar_t *wstr = PyUnicode_AsWideCharString(item, NULL); + Py_DECREF(item); + if (wstr == NULL) { + goto error; + } + + PyStatus status = PyWideStringList_Append(&wstrlist, wstr); + PyMem_Free(wstr); + if (_PyStatus_EXCEPTION(status)) { + PyErr_NoMemory(); + goto error; + } + } + + if (_PyWideStringList_Copy(result, &wstrlist) < 0) { + PyErr_NoMemory(); + goto error; + } + _PyWideStringList_Clear(&wstrlist); + Py_DECREF(xoptions); + return 0; + +error: + _PyWideStringList_Clear(&wstrlist); + Py_DECREF(xoptions); + return -1; +} + + int _PyConfig_FromDict(PyConfig *config, PyObject *dict) { @@ -1324,9 +1458,17 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict) } case PyConfig_MEMBER_WSTR_LIST: { - if (config_dict_get_wstrlist(dict, spec->name, config, - (PyWideStringList*)member) < 0) { - return -1; + if (strcmp(spec->name, "xoptions") == 0) { + if (config_dict_get_xoptions(dict, spec->name, config, + (PyWideStringList*)member) < 0) { + return -1; + } + } + else { + if (config_dict_get_wstrlist(dict, spec->name, config, + (PyWideStringList*)member) < 0) { + return -1; + } } break; } @@ -3261,3 +3403,505 @@ _Py_DumpPathConfig(PyThreadState *tstate) _PyErr_SetRaisedException(tstate, exc); } + + +// --- PyConfig_Get() ------------------------------------------------------- + +static void* +config_spec_get_member(const PyConfigSpec *spec, const PyConfig *config) +{ + return (char *)config + spec->offset; +} + + +static const PyConfigSpec* +config_generic_find_spec(const PyConfigSpec *spec, const char *name) +{ + for (; spec->name != NULL; spec++) { + if (spec->visibility == PyConfig_MEMBER_INIT_ONLY) { + continue; + } + if (strcmp(name, spec->name) == 0) { + return spec; + } + } + return NULL; +} + + +static const PyConfigSpec* +config_find_spec(const char *name) +{ + return config_generic_find_spec(PYCONFIG_SPEC, name); +} + + +static const PyConfigSpec* +preconfig_find_spec(const char *name) +{ + return config_generic_find_spec(PYPRECONFIG_SPEC, name); +} + + +static int +config_add_xoption(PyObject *dict, const wchar_t *str) +{ + PyObject *name = NULL, *value = NULL; + + const wchar_t *name_end = wcschr(str, L'='); + if (!name_end) { + name = PyUnicode_FromWideChar(str, -1); + if (name == NULL) { + goto error; + } + value = Py_NewRef(Py_True); + } + else { + name = PyUnicode_FromWideChar(str, name_end - str); + if (name == NULL) { + goto error; + } + value = PyUnicode_FromWideChar(name_end + 1, -1); + if (value == NULL) { + goto error; + } + } + if (PyDict_SetItem(dict, name, value) < 0) { + goto error; + } + Py_DECREF(name); + Py_DECREF(value); + return 0; + +error: + Py_XDECREF(name); + Py_XDECREF(value); + return -1; +} + + +PyObject* +_PyConfig_CreateXOptionsDict(const PyConfig *config) +{ + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + + Py_ssize_t nxoption = config->xoptions.length; + wchar_t **xoptions = config->xoptions.items; + for (Py_ssize_t i=0; i < nxoption; i++) { + const wchar_t *option = xoptions[i]; + if (config_add_xoption(dict, option) < 0) { + Py_DECREF(dict); + return NULL; + } + } + return dict; +} + + +static PyObject* +config_get_sys(const char *name) +{ + PyObject *value = PySys_GetObject(name); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", name); + return NULL; + } + return Py_NewRef(value); +} + + +static int +config_get_sys_write_bytecode(const PyConfig *config, int *value) +{ + PyObject *attr = config_get_sys("dont_write_bytecode"); + if (attr == NULL) { + return -1; + } + + int is_true = PyObject_IsTrue(attr); + Py_DECREF(attr); + if (is_true < 0) { + return -1; + } + *value = (!is_true); + return 0; +} + + +static PyObject* +config_get(const PyConfig *config, const PyConfigSpec *spec, + int use_sys) +{ + if (use_sys) { + if (spec->sys.attr != NULL) { + return config_get_sys(spec->sys.attr); + } + + if (strcmp(spec->name, "write_bytecode") == 0) { + int value; + if (config_get_sys_write_bytecode(config, &value) < 0) { + return NULL; + } + return PyBool_FromLong(value); + } + + if (strcmp(spec->name, "int_max_str_digits") == 0) { + PyInterpreterState *interp = _PyInterpreterState_GET(); + return PyLong_FromLong(interp->long_state.max_str_digits); + } + } + + void *member = config_spec_get_member(spec, config); + switch (spec->type) { + case PyConfig_MEMBER_INT: + case PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + + case PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + + case PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + + case PyConfig_MEMBER_WSTR: + case PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + + case PyConfig_MEMBER_WSTR_LIST: + { + if (strcmp(spec->name, "xoptions") == 0) { + return _PyConfig_CreateXOptionsDict(config); + } + else { + const PyWideStringList *list = (const PyWideStringList *)member; + return _PyWideStringList_AsTuple(list); + } + } + + default: + Py_UNREACHABLE(); + } +} + + +static PyObject* +preconfig_get(const PyPreConfig *preconfig, const PyConfigSpec *spec) +{ + // The type of all PYPRECONFIG_SPEC members is INT or BOOL. + assert(spec->type == PyConfig_MEMBER_INT + || spec->type == PyConfig_MEMBER_BOOL); + + char *member = (char *)preconfig + spec->offset; + int value = *(int *)member; + + if (spec->type == PyConfig_MEMBER_BOOL) { + return PyBool_FromLong(value != 0); + } + else { + return PyLong_FromLong(value); + } +} + + +static void +config_unknown_name_error(const char *name) +{ + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); +} + + +PyObject* +PyConfig_Get(const char *name) +{ + const PyConfigSpec *spec = config_find_spec(name); + if (spec != NULL) { + const PyConfig *config = _Py_GetConfig(); + return config_get(config, spec, 1); + } + + spec = preconfig_find_spec(name); + if (spec != NULL) { + const PyPreConfig *preconfig = &_PyRuntime.preconfig; + return preconfig_get(preconfig, spec); + } + + config_unknown_name_error(name); + return NULL; +} + + +int +PyConfig_GetInt(const char *name, int *value) +{ + assert(!PyErr_Occurred()); + + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} + + +static int +config_names_add(PyObject *names, const PyConfigSpec *spec) +{ + for (; spec->name != NULL; spec++) { + if (spec->visibility == PyConfig_MEMBER_INIT_ONLY) { + continue; + } + PyObject *name = PyUnicode_FromString(spec->name); + if (name == NULL) { + return -1; + } + int res = PyList_Append(names, name); + Py_DECREF(name); + if (res < 0) { + return -1; + } + } + return 0; +} + + +PyObject* +PyConfig_Names(void) +{ + PyObject *names = PyList_New(0); + if (names == NULL) { + goto error; + } + + if (config_names_add(names, PYCONFIG_SPEC) < 0) { + goto error; + } + if (config_names_add(names, PYPRECONFIG_SPEC) < 0) { + goto error; + } + + PyObject *frozen = PyFrozenSet_New(names); + Py_DECREF(names); + return frozen; + +error: + Py_XDECREF(names); + return NULL; +} + + +// --- PyConfig_Set() ------------------------------------------------------- + +static int +config_set_sys_flag(const PyConfigSpec *spec, int int_value) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyConfig *config = &interp->config; + + if (spec->type == PyConfig_MEMBER_BOOL) { + if (int_value != 0) { + // convert values < 0 and values > 1 to 1 + int_value = 1; + } + } + + PyObject *value; + if (spec->sys.flag_setter) { + value = spec->sys.flag_setter(int_value); + } + else { + value = config_sys_flag_long(int_value); + } + if (value == NULL) { + return -1; + } + + // Set sys.flags.FLAG + Py_ssize_t pos = spec->sys.flag_index; + if (_PySys_SetFlagObj(pos, value) < 0) { + goto error; + } + + // Set PyConfig.ATTR + assert(spec->type == PyConfig_MEMBER_INT + || spec->type == PyConfig_MEMBER_UINT + || spec->type == PyConfig_MEMBER_BOOL); + int *member = config_spec_get_member(spec, config); + *member = int_value; + + // Set sys.dont_write_bytecode attribute + if (strcmp(spec->name, "write_bytecode") == 0) { + if (PySys_SetObject("dont_write_bytecode", value) < 0) { + goto error; + } + } + + Py_DECREF(value); + return 0; + +error: + Py_DECREF(value); + return -1; +} + + +int +PyConfig_Set(const char *name, PyObject *value) +{ + const PyConfigSpec *spec = config_find_spec(name); + if (spec == NULL) { + spec = preconfig_find_spec(name); + if (spec == NULL) { + config_unknown_name_error(name); + return -1; + } + assert(spec->visibility != PyConfig_MEMBER_PUBLIC); + } + + if (spec->visibility != PyConfig_MEMBER_PUBLIC) { + PyErr_Format(PyExc_ValueError, "cannot set read-only option %s", + name); + return -1; + } + + int int_value = 0; + int has_int_value = 0; + + switch (spec->type) { + case PyConfig_MEMBER_INT: + case PyConfig_MEMBER_UINT: + case PyConfig_MEMBER_BOOL: + if (!PyLong_Check(value)) { + PyErr_Format(PyExc_TypeError, "expected int or bool, got %T", value); + return -1; + } + int_value = PyLong_AsInt(value); + if (int_value == -1 && PyErr_Occurred()) { + return -1; + } + if (int_value < 0 && spec->type != PyConfig_MEMBER_INT) { + PyErr_Format(PyExc_ValueError, "value must be >= 0"); + return -1; + } + has_int_value = 1; + break; + + case PyConfig_MEMBER_ULONG: + // not implemented: only hash_seed uses this type, and it's read-only + goto cannot_set; + + case PyConfig_MEMBER_WSTR: + if (!PyUnicode_CheckExact(value)) { + PyErr_Format(PyExc_TypeError, "expected str, got %T", value); + return -1; + } + break; + + case PyConfig_MEMBER_WSTR_OPT: + if (value != Py_None && !PyUnicode_CheckExact(value)) { + PyErr_Format(PyExc_TypeError, "expected str or None, got %T", value); + return -1; + } + break; + + case PyConfig_MEMBER_WSTR_LIST: + if (strcmp(spec->name, "xoptions") != 0) { + if (!PyList_Check(value)) { + PyErr_Format(PyExc_TypeError, "expected list[str], got %T", + value); + return -1; + } + for (Py_ssize_t i=0; i < PyList_GET_SIZE(value); i++) { + PyObject *item = PyList_GET_ITEM(value, i); + if (!PyUnicode_Check(item)) { + PyErr_Format(PyExc_TypeError, + "expected str, list item %zd has type %T", + i, item); + return -1; + } + } + } + else { + // xoptions type is dict[str, str] + if (!PyDict_Check(value)) { + PyErr_Format(PyExc_TypeError, + "expected dict[str, str | bool], got %T", + value); + return -1; + } + + Py_ssize_t pos = 0; + PyObject *key, *item; + while (PyDict_Next(value, &pos, &key, &item)) { + if (!PyUnicode_Check(key)) { + PyErr_Format(PyExc_TypeError, + "expected str, " + "got dict key type %T", key); + return -1; + } + if (!PyUnicode_Check(item) && !PyBool_Check(item)) { + PyErr_Format(PyExc_TypeError, + "expected str or bool, " + "got dict value type %T", key); + return -1; + } + } + } + break; + + default: + Py_UNREACHABLE(); + } + + + if (spec->sys.attr != NULL) { + // Set the sys attribute, but don't set PyInterpreterState.config + // to keep the code simple. + return PySys_SetObject(spec->sys.attr, value); + } + else if (spec->sys.flag_index >= 0 && has_int_value) { + return config_set_sys_flag(spec, int_value); + } + else if (strcmp(spec->name, "int_max_str_digits") == 0 && has_int_value) { + return _PySys_SetIntMaxStrDigits(int_value); + } + +cannot_set: + PyErr_Format(PyExc_ValueError, "cannot set option %s", name); + return -1; +} diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 1fff7e41767..887a916563a 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1845,6 +1845,7 @@ sys_get_int_max_str_digits_impl(PyObject *module) return PyLong_FromLong(interp->long_state.max_str_digits); } + /*[clinic input] sys.set_int_max_str_digits @@ -1857,16 +1858,10 @@ static PyObject * 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)) { - tstate->interp->long_state.max_str_digits = maxdigits; - Py_RETURN_NONE; - } else { - PyErr_Format( - PyExc_ValueError, "maxdigits must be 0 or larger than %d", - _PY_LONG_MAX_STR_DIGITS_THRESHOLD); + if (_PySys_SetIntMaxStrDigits(maxdigits) < 0) { return NULL; } + Py_RETURN_NONE; } size_t @@ -3120,6 +3115,8 @@ static PyStructSequence_Field flags_fields[] = { {0} }; +#define SYS_FLAGS_INT_MAX_STR_DIGITS 17 + static PyStructSequence_Desc flags_desc = { "sys.flags", /* name */ flags__doc__, /* doc */ @@ -3127,6 +3124,48 @@ static PyStructSequence_Desc flags_desc = { 18 }; +static void +sys_set_flag(PyObject *flags, Py_ssize_t pos, PyObject *value) +{ + assert(pos >= 0 && pos < (Py_ssize_t)(Py_ARRAY_LENGTH(flags_fields) - 1)); + + PyObject *old_value = PyStructSequence_GET_ITEM(flags, pos); + PyStructSequence_SET_ITEM(flags, pos, Py_NewRef(value)); + Py_XDECREF(old_value); +} + + +int +_PySys_SetFlagObj(Py_ssize_t pos, PyObject *value) +{ + PyObject *flags = Py_XNewRef(PySys_GetObject("flags")); + if (flags == NULL) { + if (!PyErr_Occurred()) { + PyErr_SetString(PyExc_RuntimeError, "lost sys.flags"); + } + return -1; + } + + sys_set_flag(flags, pos, value); + Py_DECREF(flags); + return 0; +} + + +static int +_PySys_SetFlagInt(Py_ssize_t pos, int value) +{ + PyObject *obj = PyLong_FromLong(value); + if (obj == NULL) { + return -1; + } + + int res = _PySys_SetFlagObj(pos, obj); + Py_DECREF(obj); + return res; +} + + static int set_flags_from_config(PyInterpreterState *interp, PyObject *flags) { @@ -3142,8 +3181,8 @@ set_flags_from_config(PyInterpreterState *interp, PyObject *flags) if (value == NULL) { \ return -1; \ } \ - Py_XDECREF(PyStructSequence_GET_ITEM(flags, pos)); \ - PyStructSequence_SET_ITEM(flags, pos, value); \ + sys_set_flag(flags, pos, value); \ + Py_DECREF(value); \ pos++; \ } while (0) #define SetFlag(expr) SetFlagObj(PyLong_FromLong(expr)) @@ -3599,64 +3638,6 @@ err_occurred: return _PyStatus_ERR("can't initialize sys module"); } -static int -sys_add_xoption(PyObject *opts, const wchar_t *s) -{ - PyObject *name, *value = NULL; - - const wchar_t *name_end = wcschr(s, L'='); - if (!name_end) { - name = PyUnicode_FromWideChar(s, -1); - if (name == NULL) { - goto error; - } - value = Py_NewRef(Py_True); - } - else { - name = PyUnicode_FromWideChar(s, name_end - s); - if (name == NULL) { - goto error; - } - value = PyUnicode_FromWideChar(name_end + 1, -1); - if (value == NULL) { - goto error; - } - } - if (PyDict_SetItem(opts, name, value) < 0) { - goto error; - } - Py_DECREF(name); - Py_DECREF(value); - return 0; - -error: - Py_XDECREF(name); - Py_XDECREF(value); - return -1; -} - - -static PyObject* -sys_create_xoptions_dict(const PyConfig *config) -{ - Py_ssize_t nxoption = config->xoptions.length; - wchar_t * const * xoptions = config->xoptions.items; - PyObject *dict = PyDict_New(); - if (dict == NULL) { - return NULL; - } - - for (Py_ssize_t i=0; i < nxoption; i++) { - const wchar_t *option = xoptions[i]; - if (sys_add_xoption(dict, option) < 0) { - Py_DECREF(dict); - return NULL; - } - } - - return dict; -} - // Update sys attributes for a new PyConfig configuration. // This function also adds attributes that _PySys_InitCore() didn't add. @@ -3703,7 +3684,7 @@ _PySys_UpdateConfig(PyThreadState *tstate) COPY_LIST("orig_argv", config->orig_argv); COPY_LIST("warnoptions", config->warnoptions); - SET_SYS("_xoptions", sys_create_xoptions_dict(config)); + SET_SYS("_xoptions", _PyConfig_CreateXOptionsDict(config)); const wchar_t *stdlibdir = _Py_GetStdlibDir(); if (stdlibdir != NULL) { @@ -4129,3 +4110,28 @@ PySys_FormatStderr(const char *format, ...) sys_format(&_Py_ID(stderr), stderr, format, va); va_end(va); } + + +int +_PySys_SetIntMaxStrDigits(int maxdigits) +{ + if (maxdigits != 0 && maxdigits < _PY_LONG_MAX_STR_DIGITS_THRESHOLD) { + PyErr_Format( + PyExc_ValueError, "maxdigits must be 0 or larger than %d", + _PY_LONG_MAX_STR_DIGITS_THRESHOLD); + return -1; + } + + // Set sys.flags.int_max_str_digits + const Py_ssize_t pos = SYS_FLAGS_INT_MAX_STR_DIGITS; + if (_PySys_SetFlagInt(pos, maxdigits) < 0) { + return -1; + } + + // Set PyInterpreterState.long_state.max_str_digits + // and PyInterpreterState.config.int_max_str_digits. + PyInterpreterState *interp = _PyInterpreterState_GET(); + interp->long_state.max_str_digits = maxdigits; + interp->config.int_max_str_digits = maxdigits; + return 0; +} diff --git a/Tools/c-analyzer/cpython/_parser.py b/Tools/c-analyzer/cpython/_parser.py index 4b75dca86ed..3a73f65f8ff 100644 --- a/Tools/c-analyzer/cpython/_parser.py +++ b/Tools/c-analyzer/cpython/_parser.py @@ -329,6 +329,7 @@ MAX_SIZES = { _abs('Python/parking_lot.c'): (40_000, 1000), _abs('Python/pylifecycle.c'): (500_000, 5000), _abs('Python/pystate.c'): (500_000, 5000), + _abs('Python/initconfig.c'): (50_000, 500), # Generated files: _abs('Include/internal/pycore_opcode.h'): (10_000, 1000), diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index b91b11763fb..bce64ed0ed6 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -90,6 +90,7 @@ Python/initconfig.c - _Py_StandardStreamErrors - # Internal constant list Python/initconfig.c - PYCONFIG_SPEC - +Python/initconfig.c - PYPRECONFIG_SPEC - ##-----------------------