bpo-36763: Add _PyCoreConfig_InitPythonConfig() (GH-13388)

Add new functions to get the Python interpreter behavior:

* _PyPreConfig_InitPythonConfig()
* _PyCoreConfig_InitPythonConfig()

Add new functions to get an isolated configuration:

* _PyPreConfig_InitIsolatedConfig()
* _PyCoreConfig_InitIsolatedConfig()

Replace _PyPreConfig_INIT and _PyCoreConfig_INIT with new functions
_PyPreConfig_Init() and _PyCoreConfig_Init().

_PyCoreConfig: set configure_c_stdio and parse_argv to 0 by default
to behave as Python 3.6 in the default configuration.

_PyCoreConfig_Read() no longer sets coerce_c_locale_warn to 1 if it's
equal to 0. coerce_c_locale_warn must now be set to -1 (ex: using
_PyCoreConfig_InitPythonConfig()) to enable C locale coercion
warning.

Add unit tests for _PyCoreConfig_InitPythonConfig()
and _PyCoreConfig_InitIsolatedConfig().

Changes:

* Rename _PyCoreConfig_GetCoreConfig() to _PyPreConfig_GetCoreConfig()
* Fix core_read_precmdline(): handle parse_argv=0
* Fix _Py_PreInitializeFromCoreConfig(): pass coreconfig.argv
  to _Py_PreInitializeFromPyArgv(), except if parse_argv=0
diff --git a/Python/coreconfig.c b/Python/coreconfig.c
index 634891e..2e8f4cf 100644
--- a/Python/coreconfig.c
+++ b/Python/coreconfig.c
@@ -109,7 +109,7 @@
 /* UTF-8 mode (PEP 540): if equals to 1, use the UTF-8 encoding, and change
    stdin and stdout error handler to "surrogateescape". It is equal to
    -1 by default: unknown, will be set by Py_Main() */
-int Py_UTF8Mode = -1;
+int Py_UTF8Mode = 0;
 int Py_DebugFlag = 0; /* Needed by parser.c */
 int Py_VerboseFlag = 0; /* Needed by import.c */
 int Py_QuietFlag = 0; /* Needed by sysmodule.c */
@@ -520,6 +520,61 @@
 }
 
 
+void
+_PyCoreConfig_Init(_PyCoreConfig *config)
+{
+    *config = _PyCoreConfig_INIT;
+}
+
+
+_PyInitError
+_PyCoreConfig_InitPythonConfig(_PyCoreConfig *config)
+{
+    _PyCoreConfig_Init(config);
+
+    config->configure_c_stdio = 1;
+    config->parse_argv = 1;
+
+    return _Py_INIT_OK();
+}
+
+
+_PyInitError
+_PyCoreConfig_InitIsolatedConfig(_PyCoreConfig *config)
+{
+    _PyCoreConfig_Init(config);
+
+    /* set to 1 */
+    config->isolated = 1;
+    config->site_import = 1;
+    config->write_bytecode = 1;
+    config->buffered_stdio = 1;
+
+    /* set to 0 */
+    config->use_environment = 0;
+    config->dev_mode = 0;
+    config->install_signal_handlers = 0;
+    config->use_hash_seed = 0;
+    config->faulthandler = 0;
+    config->tracemalloc = 0;
+    config->bytes_warning = 0;
+    config->inspect = 0;
+    config->interactive = 0;
+    config->optimization_level = 0;
+    config->parser_debug = 0;
+    config->verbose = 0;
+    config->quiet = 0;
+    config->user_site_directory = 0;
+    config->configure_c_stdio = 0;
+    config->pathconfig_warnings = 0;
+#ifdef MS_WINDOWS
+    config->legacy_windows_stdio = 0;
+#endif
+
+    return _Py_INIT_OK();
+}
+
+
 /* Copy str into *config_str (duplicate the string) */
 _PyInitError
 _PyCoreConfig_SetString(wchar_t **config_str, const wchar_t *str)
@@ -2014,17 +2069,20 @@
 {
     _PyInitError err;
 
-    if (_PyWstrList_Copy(&precmdline->argv, &config->argv) < 0) {
-        return _Py_INIT_NO_MEMORY();
+    if (config->parse_argv) {
+        if (_PyWstrList_Copy(&precmdline->argv, &config->argv) < 0) {
+            return _Py_INIT_NO_MEMORY();
+        }
     }
 
-    _PyPreConfig preconfig = _PyPreConfig_INIT;
+    _PyPreConfig preconfig;
+    _PyPreConfig_Init(&preconfig);
     if (_PyPreConfig_Copy(&preconfig, &_PyRuntime.preconfig) < 0) {
         err = _Py_INIT_NO_MEMORY();
         return err;
     }
 
-    _PyCoreConfig_GetCoreConfig(&preconfig, config);
+    _PyPreConfig_GetCoreConfig(&preconfig, config);
 
     err = _PyPreCmdline_Read(precmdline, &preconfig);
     if (_Py_INIT_FAILED(err)) {
@@ -2155,6 +2213,7 @@
 _PyCoreConfig_Read(_PyCoreConfig *config)
 {
     _PyInitError err;
+    _PyWstrList orig_argv = _PyWstrList_INIT;
 
     err = _Py_PreInitializeFromCoreConfig(config, NULL);
     if (_Py_INIT_FAILED(err)) {
@@ -2163,6 +2222,10 @@
 
     _PyCoreConfig_GetGlobalConfig(config);
 
+    if (_PyWstrList_Copy(&orig_argv, &config->argv) < 0) {
+        return _Py_INIT_NO_MEMORY();
+    }
+
     _PyPreCmdline precmdline = _PyPreCmdline_INIT;
     err = core_read_precmdline(config, &precmdline);
     if (_Py_INIT_FAILED(err)) {
@@ -2185,10 +2248,7 @@
         goto done;
     }
 
-    /* precmdline.argv is a copy of config.argv which is modified
-       by config_read_cmdline() */
-    const _PyWstrList *argv = &precmdline.argv;
-    if (_Py_SetArgcArgv(argv->length, argv->items) < 0) {
+    if (_Py_SetArgcArgv(orig_argv.length, orig_argv.items) < 0) {
         err = _Py_INIT_NO_MEMORY();
         goto done;
     }
@@ -2249,6 +2309,7 @@
     err = _Py_INIT_OK();
 
 done:
+    _PyWstrList_Clear(&orig_argv);
     _PyPreCmdline_Clear(&precmdline);
     return err;
 }
diff --git a/Python/frozenmain.c b/Python/frozenmain.c
index f2499ef..0175e42 100644
--- a/Python/frozenmain.c
+++ b/Python/frozenmain.c
@@ -39,7 +39,8 @@
         }
     }
 
-    _PyCoreConfig config = _PyCoreConfig_INIT;
+    _PyCoreConfig config;
+    _PyCoreConfig_Init(&config);
     config.pathconfig_warnings = 0;   /* Suppress errors from getpath.c */
 
     if ((p = Py_GETENV("PYTHONINSPECT")) && *p != '\0')
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index 2fcb816..c8c69eb 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -392,7 +392,8 @@
     }
 
     _PyInitError err;
-    _PyCoreConfig config = _PyCoreConfig_INIT;
+    _PyCoreConfig config;
+    _PyCoreConfig_Init(&config);
 
     err = _PyCoreConfig_Read(&config);
     if (_Py_INIT_FAILED(err)) {
diff --git a/Python/preconfig.c b/Python/preconfig.c
index 2bbf8e6..7814ee0 100644
--- a/Python/preconfig.c
+++ b/Python/preconfig.c
@@ -260,6 +260,42 @@
 
 /* --- _PyPreConfig ----------------------------------------------- */
 
+void
+_PyPreConfig_Init(_PyPreConfig *config)
+{
+    *config = _PyPreConfig_INIT;
+}
+
+
+void
+_PyPreConfig_InitPythonConfig(_PyPreConfig *config)
+{
+    _PyPreConfig_Init(config);
+
+    /* Set to -1 to enable C locale coercion (PEP 538) and UTF-8 Mode (PEP 540)
+       depending on the LC_CTYPE locale, PYTHONUTF8 and PYTHONCOERCECLOCALE
+       environment variables. */
+    config->coerce_c_locale = -1;
+    config->coerce_c_locale_warn = -1;
+    config->utf8_mode = -1;
+}
+
+
+void
+_PyPreConfig_InitIsolatedConfig(_PyPreConfig *config)
+{
+    _PyPreConfig_Init(config);
+
+    config->isolated = 1;
+    config->use_environment = 0;
+#ifdef MS_WINDOWS
+    config->legacy_windows_fs_encoding = 0;
+#endif
+    config->utf8_mode = 0;
+    config->dev_mode = 0;
+}
+
+
 int
 _PyPreConfig_Copy(_PyPreConfig *config, const _PyPreConfig *config2)
 {
@@ -346,7 +382,7 @@
 
 
 void
-_PyCoreConfig_GetCoreConfig(_PyPreConfig *config,
+_PyPreConfig_GetCoreConfig(_PyPreConfig *config,
                             const _PyCoreConfig *core_config)
 {
 #define COPY_ATTR(ATTR) \
@@ -366,11 +402,11 @@
 _PyPreConfig_GetGlobalConfig(_PyPreConfig *config)
 {
 #define COPY_FLAG(ATTR, VALUE) \
-    if (config->ATTR == -1) { \
+    if (config->ATTR < 0) { \
         config->ATTR = VALUE; \
     }
 #define COPY_NOT_FLAG(ATTR, VALUE) \
-    if (config->ATTR == -1) { \
+    if (config->ATTR < 0) { \
         config->ATTR = !(VALUE); \
     }
 
@@ -379,8 +415,8 @@
 #ifdef MS_WINDOWS
     COPY_FLAG(legacy_windows_fs_encoding, Py_LegacyWindowsFSEncodingFlag);
 #endif
-    if (Py_UTF8Mode > 0) {
-        config->utf8_mode = 1;
+    if (config->utf8_mode == -2) {
+        config->utf8_mode = Py_UTF8Mode;
     }
 
 #undef COPY_FLAG
@@ -392,11 +428,11 @@
 _PyPreConfig_SetGlobalConfig(const _PyPreConfig *config)
 {
 #define COPY_FLAG(ATTR, VAR) \
-    if (config->ATTR != -1) { \
+    if (config->ATTR >= 0) { \
         VAR = config->ATTR; \
     }
 #define COPY_NOT_FLAG(ATTR, VAR) \
-    if (config->ATTR != -1) { \
+    if (config->ATTR >= 0) { \
         VAR = !config->ATTR; \
     }
 
@@ -575,7 +611,9 @@
             }
         }
         else if (strcmp(env, "warn") == 0) {
-            config->coerce_c_locale_warn = 1;
+            if (config->coerce_c_locale_warn < 0) {
+                config->coerce_c_locale_warn = 1;
+            }
         }
         else {
             if (config->coerce_c_locale < 0) {
@@ -587,19 +625,19 @@
     /* Test if coerce_c_locale equals to -1 or equals to 1:
        PYTHONCOERCECLOCALE=1 doesn't imply that the C locale is always coerced.
        It is only coerced if if the LC_CTYPE locale is "C". */
-    if (config->coerce_c_locale == 0 || config->coerce_c_locale == 2) {
-        return;
+    if (config->coerce_c_locale < 0 || config->coerce_c_locale == 1) {
+        /* The C locale enables the C locale coercion (PEP 538) */
+        if (_Py_LegacyLocaleDetected()) {
+            config->coerce_c_locale = 2;
+        }
+        else {
+            config->coerce_c_locale = 0;
+        }
     }
 
-    /* The C locale enables the C locale coercion (PEP 538) */
-    if (_Py_LegacyLocaleDetected()) {
-        config->coerce_c_locale = 2;
+    if (config->coerce_c_locale_warn < 0) {
+        config->coerce_c_locale_warn = 0;
     }
-    else {
-        config->coerce_c_locale = 0;
-    }
-
-    assert(config->coerce_c_locale >= 0);
 }
 
 
@@ -659,6 +697,7 @@
     }
 
     assert(config->coerce_c_locale >= 0);
+    assert(config->coerce_c_locale_warn >= 0);
 #ifdef MS_WINDOWS
     assert(config->legacy_windows_fs_encoding >= 0);
 #endif
@@ -700,7 +739,8 @@
     }
 
     /* Save the config to be able to restore it if encodings change */
-    _PyPreConfig save_config = _PyPreConfig_INIT;
+    _PyPreConfig save_config;
+    _PyPreConfig_Init(&save_config);
     if (_PyPreConfig_Copy(&save_config, config) < 0) {
         return _Py_INIT_NO_MEMORY();
     }
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index eecb439..231706d 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -701,7 +701,8 @@
         return _Py_INIT_OK();
     }
 
-    _PyPreConfig config = _PyPreConfig_INIT;
+    _PyPreConfig config;
+    _PyPreConfig_Init(&config);
 
     if (src_config) {
         if (_PyPreConfig_Copy(&config, src_config) < 0) {
@@ -752,13 +753,22 @@
 _Py_PreInitializeFromCoreConfig(const _PyCoreConfig *coreconfig,
                                 const _PyArgv *args)
 {
-    _PyPreConfig config = _PyPreConfig_INIT;
+    _PyPreConfig config;
+    _PyPreConfig_Init(&config);
     if (coreconfig != NULL) {
-        _PyCoreConfig_GetCoreConfig(&config, coreconfig);
+        _PyPreConfig_GetCoreConfig(&config, coreconfig);
     }
-    return _Py_PreInitializeFromPyArgv(&config, args);
-    /* No need to clear config:
-       _PyCoreConfig_GetCoreConfig() doesn't allocate memory */
+
+    if (args == NULL && coreconfig != NULL && coreconfig->parse_argv) {
+        _PyArgv config_args = {
+            .use_bytes_argv = 0,
+            .argc = coreconfig->argv.length,
+            .wchar_argv = coreconfig->argv.items};
+        return _Py_PreInitializeFromPyArgv(&config, &config_args);
+    }
+    else {
+        return _Py_PreInitializeFromPyArgv(&config, args);
+    }
 }
 
 
@@ -829,7 +839,8 @@
         return err;
     }
 
-    _PyCoreConfig local_config = _PyCoreConfig_INIT;
+    _PyCoreConfig local_config;
+    _PyCoreConfig_Init(&local_config);
     err = pyinit_coreconfig(runtime, &local_config, src_config, args, interp_p);
     _PyCoreConfig_Clear(&local_config);
     return err;
@@ -1051,7 +1062,8 @@
         return;
     }
 
-    _PyCoreConfig config = _PyCoreConfig_INIT;
+    _PyCoreConfig config;
+    _PyCoreConfig_Init(&config);
     config.install_signal_handlers = install_sigs;
 
     err = _Py_InitializeFromConfig(&config);
diff --git a/Python/pystate.c b/Python/pystate.c
index 8c906ce..2f80aa2 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -49,7 +49,7 @@
 
     _PyGC_Initialize(&runtime->gc);
     _PyEval_Initialize(&runtime->ceval);
-    runtime->preconfig = _PyPreConfig_INIT;
+    _PyPreConfig_Init(&runtime->preconfig);
 
     runtime->gilstate.check_enabled = 1;
 
@@ -189,7 +189,7 @@
     memset(interp, 0, sizeof(*interp));
     interp->id_refcount = -1;
     interp->check_interval = 100;
-    interp->core_config = _PyCoreConfig_INIT;
+    _PyCoreConfig_Init(&interp->core_config);
     interp->eval_frame = _PyEval_EvalFrameDefault;
 #ifdef HAVE_DLOPEN
 #if HAVE_DECL_RTLD_NOW