Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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>`
Expand Down Expand Up @@ -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>`
Expand All @@ -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>`
Expand Down Expand Up @@ -431,35 +436,35 @@ Configuration Options
- Public
* - ``"prefix"``
- :c:member:`prefix <PyConfig.prefix>`
- ``str``
- ``str | None``
- Public
* - ``"program_name"``
- :c:member:`program_name <PyConfig.program_name>`
- ``str``
- Read-only
* - ``"pycache_prefix"``
- :c:member:`pycache_prefix <PyConfig.pycache_prefix>`
- ``str``
- ``str | None``
- Public
* - ``"quiet"``
- :c:member:`quiet <PyConfig.quiet>`
- ``bool``
- 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>`
Expand All @@ -479,15 +484,15 @@ 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>`
- ``str``
- Read-only
* - ``"stdlib_dir"``
- :c:member:`stdlib_dir <PyConfig.stdlib_dir>`
- ``str``
- ``str | None``
- Public
* - ``"tracemalloc"``
- :c:member:`tracemalloc <PyConfig.tracemalloc>`
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -1977,6 +1977,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")

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
107 changes: 106 additions & 1 deletion Programs/_testembed.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -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},
Expand Down
13 changes: 9 additions & 4 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading