https://github.com/python/cpython/commit/6d69297b2b1007991640310e8699979a2ef66287
commit: 6d69297b2b1007991640310e8699979a2ef66287
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-11-04T01:47:50Z
summary:
gh-160962: Fix PyInitConfig_SetStr() with NULL value (#161258)
Previously, passing NULL to an optional string option crashed.
Document optional string options.
Add more tests on the PyInitConfig API.
files:
A Misc/NEWS.d/next/C_API/2026-11-03-18-27-42.gh-issue-160962.06816I.rst
M Doc/c-api/init_config.rst
M Lib/test/test_embed.py
M Programs/_testembed.c
M Python/initconfig.c
diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index ad3571262168003..af74e2d0a8369b8 100644
--- a/Doc/c-api/init_config.rst
+++ b/Doc/c-api/init_config.rst
@@ -155,8 +155,10 @@ UTF-8 encoded string. See :ref:`Configuration Options
<pyinitconfig-opts>`.
* Set *\*value*, and return ``0`` on success.
* Set an error in *config* and return ``-1`` on error.
- *\*value* can be set to ``NULL`` if the option is an optional string and the
- option is unset.
+ *\*value* can be set to ``NULL`` if the option is unset or if the option is
+ an optional string.
+ Optional strings are documented with the type ``str | None`` in the
+ :ref:`Configuration Options <pyinitconfig-opts>` table.
On success, the string must be released with ``free(value)`` if it's not
``NULL``.
@@ -204,6 +206,9 @@ only implemented when ``Py_InitializeFromInitConfig()`` is
called, not by the
Set a string configuration option from a null-terminated UTF-8
encoded string. The string is copied.
+ Options documented with the type ``str | None`` in the :ref:`Configuration
+ Options <pyinitconfig-opts>` table can be set to ``NULL``.
+
* Return ``0`` on success.
* Set an error in *config* and return ``-1`` on error.
@@ -275,15 +280,15 @@ Configuration Options
- Public
* - ``"base_exec_prefix"``
- :c:member:`base_exec_prefix <PyConfig.base_exec_prefix>`
- - ``str``
+ - ``str | None``
- Public
* - ``"base_executable"``
- :c:member:`base_executable <PyConfig.base_executable>`
- - ``str``
+ - ``str | None``
- Public
* - ``"base_prefix"``
- :c:member:`base_prefix <PyConfig.base_prefix>`
- - ``str``
+ - ``str | None``
- Public
* - ``"buffered_stdio"``
- :c:member:`buffered_stdio <PyConfig.buffered_stdio>`
@@ -331,15 +336,15 @@ Configuration Options
- Read-only
* - ``"dump_refs_file"``
- :c:member:`dump_refs_file <PyConfig.dump_refs_file>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"exec_prefix"``
- :c:member:`exec_prefix <PyConfig.exec_prefix>`
- - ``str``
+ - ``str | None``
- Public
* - ``"executable"``
- :c:member:`executable <PyConfig.executable>`
- - ``str``
+ - ``str | None``
- Public
* - ``"faulthandler"``
- :c:member:`faulthandler <PyConfig.faulthandler>`
@@ -359,7 +364,7 @@ Configuration Options
- Read-only
* - ``"home"``
- :c:member:`home <PyConfig.home>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"import_time"``
- :c:member:`import_time <PyConfig.import_time>`
@@ -431,7 +436,7 @@ Configuration Options
- Public
* - ``"prefix"``
- :c:member:`prefix <PyConfig.prefix>`
- - ``str``
+ - ``str | None``
- Public
* - ``"program_name"``
- :c:member:`program_name <PyConfig.program_name>`
@@ -439,7 +444,7 @@ Configuration Options
- Read-only
* - ``"pycache_prefix"``
- :c:member:`pycache_prefix <PyConfig.pycache_prefix>`
- - ``str``
+ - ``str | None``
- Public
* - ``"quiet"``
- :c:member:`quiet <PyConfig.quiet>`
@@ -447,19 +452,19 @@ Configuration Options
- Public
* - ``"run_command"``
- :c:member:`run_command <PyConfig.run_command>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"run_filename"``
- :c:member:`run_filename <PyConfig.run_filename>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"run_module"``
- :c:member:`run_module <PyConfig.run_module>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"run_presite"``
- :c:member:`run_presite <PyConfig.run_presite>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"safe_path"``
- :c:member:`safe_path <PyConfig.safe_path>`
@@ -479,7 +484,7 @@ Configuration Options
- Read-only
* - ``"stdio_encoding"``
- :c:member:`stdio_encoding <PyConfig.stdio_encoding>`
- - ``str``
+ - ``str | None``
- Read-only
* - ``"stdio_errors"``
- :c:member:`stdio_errors <PyConfig.stdio_errors>`
@@ -487,7 +492,7 @@ Configuration Options
- Read-only
* - ``"stdlib_dir"``
- :c:member:`stdlib_dir <PyConfig.stdlib_dir>`
- - ``str``
+ - ``str | None``
- Public
* - ``"tracemalloc"``
- :c:member:`tracemalloc <PyConfig.tracemalloc>`
@@ -542,6 +547,9 @@ Configuration Options
- ``bool``
- Read-only
+The type ``str | None`` means that the option is an optional string which can
+be set to ``NULL``.
+
Visibility:
* Public: Can be retrieved by :c:func:`PyConfig_Get` and set by
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index b5a441b7e94fb49..0317e4900ab0acc 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -1973,6 +1973,9 @@ def test_initconfig_api(self):
self.check_all_configs("test_initconfig_api", config, preconfig,
api=API_ISOLATED, ignore_stderr=True)
+ def test_initconfig_set_errors(self):
+ self.run_embedded_interpreter("test_initconfig_set_errors")
+
def test_initconfig_get_api(self):
self.run_embedded_interpreter("test_initconfig_get_api")
diff --git
a/Misc/NEWS.d/next/C_API/2026-11-03-18-27-42.gh-issue-160962.06816I.rst
b/Misc/NEWS.d/next/C_API/2026-11-03-18-27-42.gh-issue-160962.06816I.rst
new file mode 100644
index 000000000000000..3b043a8d495f1c6
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-11-03-18-27-42.gh-issue-160962.06816I.rst
@@ -0,0 +1,2 @@
+Fix :c:func:`PyInitConfig_SetStr` when setting an optional string option to
+``NULL``. Previously, the function crashed. Patch by Victor Stinner.
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 08bbcd4cfec1543..6431bfda53c95c1 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -1720,6 +1720,7 @@ static int test_initconfig_api(void)
goto error;
}
+ // Set an integer option
if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) {
goto error;
}
@@ -1746,6 +1747,11 @@ static int test_initconfig_api(void)
goto error;
}
+ // Set an optional string to NULL explicitly (must not crash).
+ if (PyInitConfig_SetStr(config, "prefix", NULL) < 0) {
+ goto error;
+ }
+
if (Py_InitializeFromInitConfig(config) < 0) {
goto error;
}
@@ -1766,6 +1772,99 @@ static int test_initconfig_api(void)
}
+static void
+check_initconfig_error(PyInitConfig *config, const char *expected)
+{
+ const char *err_msg;
+ assert(PyInitConfig_GetError(config, &err_msg) == 1);
+ assert(strcmp(err_msg, expected) == 0);
+}
+
+
+// Test errors of PyInitConfig_Set*() functions
+static int test_initconfig_set_errors(void)
+{
+ char* strlist[] = {"value"};
+
+ PyInitConfig *config = PyInitConfig_Create();
+ if (config == NULL) {
+ printf("Init allocation error\n");
+ return 1;
+ }
+
+ // Unknown option
+ assert(PyInitConfig_SetInt(config, "non-existent", 123) < 0);
+ check_initconfig_error(config, "unknown config option name");
+ assert(PyInitConfig_SetStr(config, "non-existent", "abc") < 0);
+ check_initconfig_error(config, "unknown config option name");
+ assert(PyInitConfig_SetStrList(config, "non-existent",
+ Py_ARRAY_LENGTH(strlist), strlist) < 0);
+ check_initconfig_error(config, "unknown config option name");
+
+
+ // Wrong type
+ assert(PyInitConfig_SetInt(config, "platlibdir", 123) == -1);
+ check_initconfig_error(config, "config option type is not int");
+
+ assert(PyInitConfig_SetStr(config, "cpu_count", "abc") == -1);
+ check_initconfig_error(config, "config option type is not string");
+
+ assert(PyInitConfig_SetStrList(config, "cpu_count",
+ Py_ARRAY_LENGTH(strlist), strlist) < 0);
+ check_initconfig_error(config, "config option type is not strings list");
+
+
+ // Test SetInt() range: cpu_count option type is PyConfig_MEMBER_INT
+ assert(PyInitConfig_SetInt(config, "cpu_count", INT_MIN) == 0);
+ assert(PyInitConfig_SetInt(config, "cpu_count", INT_MAX) == 0);
+
+ assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MIN) == -1);
+ check_initconfig_error(config, "config option value is out of int range");
+ assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MAX) == -1);
+ check_initconfig_error(config, "config option value is out of int range");
+
+
+ // Test SetInt() range: verbose option type is PyConfig_MEMBER_UINT
+ assert(PyInitConfig_SetInt(config, "verbose", 0) == 0);
+ assert(PyInitConfig_SetInt(config, "verbose", UINT_MAX) == 0);
+
+ assert(PyInitConfig_SetInt(config, "verbose", -1) == -1);
+ check_initconfig_error(config, "config option value is out of unsigned int
range");
+ assert(PyInitConfig_SetInt(config, "verbose", INT64_MAX) == -1);
+ check_initconfig_error(config, "config option value is out of unsigned int
range");
+
+
+ // SetStr() decode error
+ assert(PyInitConfig_SetStr(config, "platlibdir", "\xff") == -1);
+ check_initconfig_error(config, "decoding error");
+
+
+ // Setting a non-optional string to NULL fails with an error,
+ // but leaves the optional unchanged.
+ if (PyInitConfig_SetStr(config, "program_name", PROGRAM_NAME_UTF8) < 0) {
+ goto error;
+ }
+ assert(PyInitConfig_SetStr(config, "program_name", NULL) < 0);
+ check_initconfig_error(config, "config option string cannot be NULL");
+ char *str;
+ assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
+ assert(strcmp(str, PROGRAM_NAME_UTF8) == 0);
+ free(str);
+
+
+ PyInitConfig_Free(config);
+ return 0;
+
+error:
+ {
+ const char *err_msg;
+ (void)PyInitConfig_GetError(config, &err_msg);
+ printf("Python init failed: %s\n", err_msg);
+ exit(1);
+ }
+}
+
+
static int test_initconfig_get_api(void)
{
PyInitConfig *config = PyInitConfig_Create();
@@ -1778,6 +1877,7 @@ static int test_initconfig_get_api(void)
assert(PyInitConfig_HasOption(config, "verbose") == 1);
assert(PyInitConfig_HasOption(config, "utf8_mode") == 1);
assert(PyInitConfig_HasOption(config, "non-existent") == 0);
+ assert(PyInitConfig_HasOption(config, "") == 0);
// test PyInitConfig_GetInt()
assert(initconfig_getint(config, "dev_mode") == 0);
@@ -1789,7 +1889,7 @@ static int test_initconfig_get_api(void)
assert(PyInitConfig_SetInt(config, "utf8_mode", 0) == 0);
assert(initconfig_getint(config, "utf8_mode") == 0);
- // test PyInitConfig_GetStr()
+ // test PyInitConfig_GetStr() on an unset string (NULL by default)
char *str;
assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
assert(str == NULL);
@@ -1815,8 +1915,11 @@ static int test_initconfig_get_api(void)
// Setting hash_seed sets use_hash_seed
assert(initconfig_getint(config, "use_hash_seed") == 0);
+ assert(initconfig_getint(config, "hash_seed") == 0);
+
assert(PyInitConfig_SetInt(config, "hash_seed", 123) == 0);
assert(initconfig_getint(config, "use_hash_seed") == 1);
+ assert(initconfig_getint(config, "hash_seed") == 123);
// Setting module_search_paths sets module_search_paths_set
assert(initconfig_getint(config, "module_search_paths_set") == 0);
@@ -1825,6 +1928,7 @@ static int test_initconfig_get_api(void)
Py_ARRAY_LENGTH(paths), paths) == 0);
assert(initconfig_getint(config, "module_search_paths_set") == 1);
+ PyInitConfig_Free(config);
return 0;
}
@@ -3188,6 +3292,7 @@ static struct TestCase TestCases[] = {
{"test_init_is_python_build", test_init_is_python_build},
{"test_init_warnoptions", test_init_warnoptions},
{"test_initconfig_api", test_initconfig_api},
+ {"test_initconfig_set_errors", test_initconfig_set_errors},
{"test_initconfig_get_api", test_initconfig_get_api},
{"test_initconfig_exit", test_initconfig_exit},
{"test_initconfig_module", test_initconfig_module},
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 225ee0a3fe5ff2a..a768fdf0ca61c4d 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -4372,10 +4372,15 @@ PyInitConfig_SetStr(PyInitConfig *config, const char
*name, const char* value)
}
wchar_t **member = raw_member;
-
- wchar_t *wstr = utf8_to_wstr(config, value);
- if (wstr == NULL) {
- return -1;
+ wchar_t *wstr;
+ if (value != NULL) {
+ wstr = utf8_to_wstr(config, value);
+ if (wstr == NULL) {
+ return -1;
+ }
+ }
+ else {
+ wstr = NULL;
}
free(*member);
*member = wstr;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]