Skip to content

Commit 22bc41d

Browse files
[3.15] gh-160962: Fix PyInitConfig_SetStr() with NULL value (GH-161258) (#161291)
gh-160962: Fix PyInitConfig_SetStr() with NULL value (GH-161258) Previously, passing NULL to an optional string option crashed. Document optional string options. Add more tests on the PyInitConfig API. (cherry picked from commit 6d69297) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 37f4276 commit 22bc41d

5 files changed

Lines changed: 145 additions & 22 deletions

File tree

‎Doc/c-api/init_config.rst‎

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ UTF-8 encoded string. See :ref:`Configuration Options <pyinitconfig-opts>`.
156156
* Set *\*value*, and return ``0`` on success.
157157
* Set an error in *config* and return ``-1`` on error.
158158
159-
*\*value* can be set to ``NULL`` if the option is an optional string and the
160-
option is unset.
159+
*\*value* can be set to ``NULL`` if the option is unset or if the option is
160+
an optional string.
161+
Optional strings are documented with the type ``str | None`` in the
162+
:ref:`Configuration Options <pyinitconfig-opts>` table.
161163
162164
On success, the string must be released with ``free(value)`` if it's not
163165
``NULL``.
@@ -205,6 +207,9 @@ only implemented when ``Py_InitializeFromInitConfig()`` is called, not by the
205207
Set a string configuration option from a null-terminated UTF-8
206208
encoded string. The string is copied.
207209
210+
Options documented with the type ``str | None`` in the :ref:`Configuration
211+
Options <pyinitconfig-opts>` table can be set to ``NULL``.
212+
208213
* Return ``0`` on success.
209214
* Set an error in *config* and return ``-1`` on error.
210215
@@ -275,15 +280,15 @@ Configuration Options
275280
- Public
276281
* - ``"base_exec_prefix"``
277282
- :c:member:`base_exec_prefix <PyConfig.base_exec_prefix>`
278-
- ``str``
283+
- ``str | None``
279284
- Public
280285
* - ``"base_executable"``
281286
- :c:member:`base_executable <PyConfig.base_executable>`
282-
- ``str``
287+
- ``str | None``
283288
- Public
284289
* - ``"base_prefix"``
285290
- :c:member:`base_prefix <PyConfig.base_prefix>`
286-
- ``str``
291+
- ``str | None``
287292
- Public
288293
* - ``"buffered_stdio"``
289294
- :c:member:`buffered_stdio <PyConfig.buffered_stdio>`
@@ -331,15 +336,15 @@ Configuration Options
331336
- Read-only
332337
* - ``"dump_refs_file"``
333338
- :c:member:`dump_refs_file <PyConfig.dump_refs_file>`
334-
- ``str``
339+
- ``str | None``
335340
- Read-only
336341
* - ``"exec_prefix"``
337342
- :c:member:`exec_prefix <PyConfig.exec_prefix>`
338-
- ``str``
343+
- ``str | None``
339344
- Public
340345
* - ``"executable"``
341346
- :c:member:`executable <PyConfig.executable>`
342-
- ``str``
347+
- ``str | None``
343348
- Public
344349
* - ``"faulthandler"``
345350
- :c:member:`faulthandler <PyConfig.faulthandler>`
@@ -359,7 +364,7 @@ Configuration Options
359364
- Read-only
360365
* - ``"home"``
361366
- :c:member:`home <PyConfig.home>`
362-
- ``str``
367+
- ``str | None``
363368
- Read-only
364369
* - ``"import_time"``
365370
- :c:member:`import_time <PyConfig.import_time>`
@@ -431,35 +436,35 @@ Configuration Options
431436
- Public
432437
* - ``"prefix"``
433438
- :c:member:`prefix <PyConfig.prefix>`
434-
- ``str``
439+
- ``str | None``
435440
- Public
436441
* - ``"program_name"``
437442
- :c:member:`program_name <PyConfig.program_name>`
438443
- ``str``
439444
- Read-only
440445
* - ``"pycache_prefix"``
441446
- :c:member:`pycache_prefix <PyConfig.pycache_prefix>`
442-
- ``str``
447+
- ``str | None``
443448
- Public
444449
* - ``"quiet"``
445450
- :c:member:`quiet <PyConfig.quiet>`
446451
- ``bool``
447452
- Public
448453
* - ``"run_command"``
449454
- :c:member:`run_command <PyConfig.run_command>`
450-
- ``str``
455+
- ``str | None``
451456
- Read-only
452457
* - ``"run_filename"``
453458
- :c:member:`run_filename <PyConfig.run_filename>`
454-
- ``str``
459+
- ``str | None``
455460
- Read-only
456461
* - ``"run_module"``
457462
- :c:member:`run_module <PyConfig.run_module>`
458-
- ``str``
463+
- ``str | None``
459464
- Read-only
460465
* - ``"run_presite"``
461466
- :c:member:`run_presite <PyConfig.run_presite>`
462-
- ``str``
467+
- ``str | None``
463468
- Read-only
464469
* - ``"safe_path"``
465470
- :c:member:`safe_path <PyConfig.safe_path>`
@@ -479,15 +484,15 @@ Configuration Options
479484
- Read-only
480485
* - ``"stdio_encoding"``
481486
- :c:member:`stdio_encoding <PyConfig.stdio_encoding>`
482-
- ``str``
487+
- ``str | None``
483488
- Read-only
484489
* - ``"stdio_errors"``
485490
- :c:member:`stdio_errors <PyConfig.stdio_errors>`
486491
- ``str``
487492
- Read-only
488493
* - ``"stdlib_dir"``
489494
- :c:member:`stdlib_dir <PyConfig.stdlib_dir>`
490-
- ``str``
495+
- ``str | None``
491496
- Public
492497
* - ``"tracemalloc"``
493498
- :c:member:`tracemalloc <PyConfig.tracemalloc>`
@@ -542,6 +547,9 @@ Configuration Options
542547
- ``bool``
543548
- Read-only
544549
550+
The type ``str | None`` means that the option is an optional string which can
551+
be set to ``NULL``.
552+
545553
Visibility:
546554
547555
* Public: Can be retrieved by :c:func:`PyConfig_Get` and set by

‎Lib/test/test_embed.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1968,6 +1968,9 @@ def test_initconfig_api(self):
19681968
self.check_all_configs("test_initconfig_api", config, preconfig,
19691969
api=API_ISOLATED, ignore_stderr=True)
19701970

1971+
def test_initconfig_set_errors(self):
1972+
self.run_embedded_interpreter("test_initconfig_set_errors")
1973+
19711974
def test_initconfig_get_api(self):
19721975
self.run_embedded_interpreter("test_initconfig_get_api")
19731976

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :c:func:`PyInitConfig_SetStr` when setting an optional string option to
2+
``NULL``. Previously, the function crashed. Patch by Victor Stinner.

‎Programs/_testembed.c‎

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1801,6 +1801,7 @@ static int test_initconfig_api(void)
18011801
goto error;
18021802
}
18031803

1804+
// Set an integer option
18041805
if (PyInitConfig_SetInt(config, "hash_seed", 10) < 0) {
18051806
goto error;
18061807
}
@@ -1827,6 +1828,11 @@ static int test_initconfig_api(void)
18271828
goto error;
18281829
}
18291830

1831+
// Set an optional string to NULL explicitly (must not crash).
1832+
if (PyInitConfig_SetStr(config, "prefix", NULL) < 0) {
1833+
goto error;
1834+
}
1835+
18301836
if (Py_InitializeFromInitConfig(config) < 0) {
18311837
goto error;
18321838
}
@@ -1847,6 +1853,99 @@ static int test_initconfig_api(void)
18471853
}
18481854

18491855

1856+
static void
1857+
check_initconfig_error(PyInitConfig *config, const char *expected)
1858+
{
1859+
const char *err_msg;
1860+
assert(PyInitConfig_GetError(config, &err_msg) == 1);
1861+
assert(strcmp(err_msg, expected) == 0);
1862+
}
1863+
1864+
1865+
// Test errors of PyInitConfig_Set*() functions
1866+
static int test_initconfig_set_errors(void)
1867+
{
1868+
char* strlist[] = {"value"};
1869+
1870+
PyInitConfig *config = PyInitConfig_Create();
1871+
if (config == NULL) {
1872+
printf("Init allocation error\n");
1873+
return 1;
1874+
}
1875+
1876+
// Unknown option
1877+
assert(PyInitConfig_SetInt(config, "non-existent", 123) < 0);
1878+
check_initconfig_error(config, "unknown config option name");
1879+
assert(PyInitConfig_SetStr(config, "non-existent", "abc") < 0);
1880+
check_initconfig_error(config, "unknown config option name");
1881+
assert(PyInitConfig_SetStrList(config, "non-existent",
1882+
Py_ARRAY_LENGTH(strlist), strlist) < 0);
1883+
check_initconfig_error(config, "unknown config option name");
1884+
1885+
1886+
// Wrong type
1887+
assert(PyInitConfig_SetInt(config, "platlibdir", 123) == -1);
1888+
check_initconfig_error(config, "config option type is not int");
1889+
1890+
assert(PyInitConfig_SetStr(config, "cpu_count", "abc") == -1);
1891+
check_initconfig_error(config, "config option type is not string");
1892+
1893+
assert(PyInitConfig_SetStrList(config, "cpu_count",
1894+
Py_ARRAY_LENGTH(strlist), strlist) < 0);
1895+
check_initconfig_error(config, "config option type is not strings list");
1896+
1897+
1898+
// Test SetInt() range: cpu_count option type is PyConfig_MEMBER_INT
1899+
assert(PyInitConfig_SetInt(config, "cpu_count", INT_MIN) == 0);
1900+
assert(PyInitConfig_SetInt(config, "cpu_count", INT_MAX) == 0);
1901+
1902+
assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MIN) == -1);
1903+
check_initconfig_error(config, "config option value is out of int range");
1904+
assert(PyInitConfig_SetInt(config, "cpu_count", INT64_MAX) == -1);
1905+
check_initconfig_error(config, "config option value is out of int range");
1906+
1907+
1908+
// Test SetInt() range: verbose option type is PyConfig_MEMBER_UINT
1909+
assert(PyInitConfig_SetInt(config, "verbose", 0) == 0);
1910+
assert(PyInitConfig_SetInt(config, "verbose", UINT_MAX) == 0);
1911+
1912+
assert(PyInitConfig_SetInt(config, "verbose", -1) == -1);
1913+
check_initconfig_error(config, "config option value is out of unsigned int range");
1914+
assert(PyInitConfig_SetInt(config, "verbose", INT64_MAX) == -1);
1915+
check_initconfig_error(config, "config option value is out of unsigned int range");
1916+
1917+
1918+
// SetStr() decode error
1919+
assert(PyInitConfig_SetStr(config, "platlibdir", "\xff") == -1);
1920+
check_initconfig_error(config, "decoding error");
1921+
1922+
1923+
// Setting a non-optional string to NULL fails with an error,
1924+
// but leaves the optional unchanged.
1925+
if (PyInitConfig_SetStr(config, "program_name", PROGRAM_NAME_UTF8) < 0) {
1926+
goto error;
1927+
}
1928+
assert(PyInitConfig_SetStr(config, "program_name", NULL) < 0);
1929+
check_initconfig_error(config, "config option string cannot be NULL");
1930+
char *str;
1931+
assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
1932+
assert(strcmp(str, PROGRAM_NAME_UTF8) == 0);
1933+
free(str);
1934+
1935+
1936+
PyInitConfig_Free(config);
1937+
return 0;
1938+
1939+
error:
1940+
{
1941+
const char *err_msg;
1942+
(void)PyInitConfig_GetError(config, &err_msg);
1943+
printf("Python init failed: %s\n", err_msg);
1944+
exit(1);
1945+
}
1946+
}
1947+
1948+
18501949
static int test_initconfig_get_api(void)
18511950
{
18521951
PyInitConfig *config = PyInitConfig_Create();
@@ -1859,6 +1958,7 @@ static int test_initconfig_get_api(void)
18591958
assert(PyInitConfig_HasOption(config, "verbose") == 1);
18601959
assert(PyInitConfig_HasOption(config, "utf8_mode") == 1);
18611960
assert(PyInitConfig_HasOption(config, "non-existent") == 0);
1961+
assert(PyInitConfig_HasOption(config, "") == 0);
18621962

18631963
// test PyInitConfig_GetInt()
18641964
assert(initconfig_getint(config, "dev_mode") == 0);
@@ -1870,7 +1970,7 @@ static int test_initconfig_get_api(void)
18701970
assert(PyInitConfig_SetInt(config, "utf8_mode", 0) == 0);
18711971
assert(initconfig_getint(config, "utf8_mode") == 0);
18721972

1873-
// test PyInitConfig_GetStr()
1973+
// test PyInitConfig_GetStr() on an unset string (NULL by default)
18741974
char *str;
18751975
assert(PyInitConfig_GetStr(config, "program_name", &str) == 0);
18761976
assert(str == NULL);
@@ -1896,8 +1996,11 @@ static int test_initconfig_get_api(void)
18961996

18971997
// Setting hash_seed sets use_hash_seed
18981998
assert(initconfig_getint(config, "use_hash_seed") == 0);
1999+
assert(initconfig_getint(config, "hash_seed") == 0);
2000+
18992001
assert(PyInitConfig_SetInt(config, "hash_seed", 123) == 0);
19002002
assert(initconfig_getint(config, "use_hash_seed") == 1);
2003+
assert(initconfig_getint(config, "hash_seed") == 123);
19012004

19022005
// Setting module_search_paths sets module_search_paths_set
19032006
assert(initconfig_getint(config, "module_search_paths_set") == 0);
@@ -1906,6 +2009,7 @@ static int test_initconfig_get_api(void)
19062009
Py_ARRAY_LENGTH(paths), paths) == 0);
19072010
assert(initconfig_getint(config, "module_search_paths_set") == 1);
19082011

2012+
PyInitConfig_Free(config);
19092013
return 0;
19102014
}
19112015

@@ -3179,6 +3283,7 @@ static struct TestCase TestCases[] = {
31793283
{"test_init_is_python_build", test_init_is_python_build},
31803284
{"test_init_warnoptions", test_init_warnoptions},
31813285
{"test_initconfig_api", test_initconfig_api},
3286+
{"test_initconfig_set_errors", test_initconfig_set_errors},
31823287
{"test_initconfig_get_api", test_initconfig_get_api},
31833288
{"test_initconfig_exit", test_initconfig_exit},
31843289
{"test_initconfig_module", test_initconfig_module},

‎Python/initconfig.c‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4361,10 +4361,15 @@ PyInitConfig_SetStr(PyInitConfig *config, const char *name, const char* value)
43614361
}
43624362

43634363
wchar_t **member = raw_member;
4364-
4365-
wchar_t *wstr = utf8_to_wstr(config, value);
4366-
if (wstr == NULL) {
4367-
return -1;
4364+
wchar_t *wstr;
4365+
if (value != NULL) {
4366+
wstr = utf8_to_wstr(config, value);
4367+
if (wstr == NULL) {
4368+
return -1;
4369+
}
4370+
}
4371+
else {
4372+
wstr = NULL;
43684373
}
43694374
free(*member);
43704375
*member = wstr;

0 commit comments

Comments
 (0)