bpo-38234: Fix _PyConfig_InitPathConfig() (GH-16335) (GH-16336)

* _PyConfig_InitPathConfig() now starts by copying the global path
  configuration, and then override values set in PyConfig.
* _PyPathConfig_Calculate() implementations no longer override
  _PyPathConfig fields which are already computed. For example,
  if _PyPathConfig.prefix is not NULL, leave it unchanged.
* If Py_SetPath() has been called, _PyConfig_InitPathConfig() doesn't
  call _PyPathConfig_Calculate() anymore.
* _PyPathConfig_Calculate() no longer uses PyConfig,
  except to initialize PyCalculatePath structure.
* pathconfig_calculate(): remove useless temporary
  "_PyPathConfig new_config" variable.
* calculate_module_search_path(): remove hack to workaround memory
  allocation failure, call Py_FatalError() instead.
* Fix get_program_full_path(): handle memory allocation failure.

(cherry picked from commit 9c42f8cda552694f3b47d6388d4ae84d61731872)
diff --git a/Python/pathconfig.c b/Python/pathconfig.c
index 1934f2e..0953310 100644
--- a/Python/pathconfig.c
+++ b/Python/pathconfig.c
@@ -51,12 +51,12 @@
         ATTR = NULL; \
     } while (0)
 
-    CLEAR(config->prefix);
     CLEAR(config->program_full_path);
+    CLEAR(config->prefix);
     CLEAR(config->exec_prefix);
     CLEAR(config->module_search_path);
-    CLEAR(config->home);
     CLEAR(config->program_name);
+    CLEAR(config->home);
     CLEAR(config->base_executable);
 #undef CLEAR
 
@@ -64,60 +64,31 @@
 }
 
 
-/* Calculate the path configuration: initialize pathconfig from config */
 static PyStatus
-pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config)
+pathconfig_copy(_PyPathConfig *config, const _PyPathConfig *config2)
 {
-    PyStatus status;
-    _PyPathConfig new_config = _PyPathConfig_INIT;
+    pathconfig_clear(config);
 
-    PyMemAllocatorEx old_alloc;
-    _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+#define COPY_ATTR(ATTR) \
+    do { \
+        if (copy_wstr(&config->ATTR, config2->ATTR) < 0) { \
+            return _PyStatus_NO_MEMORY(); \
+        } \
+    } while (0)
 
-    if (copy_wstr(&new_config.module_search_path,
-                  _Py_path_config.module_search_path) < 0)
-    {
-        status = _PyStatus_NO_MEMORY();
-        goto error;
-    }
+    COPY_ATTR(program_full_path);
+    COPY_ATTR(prefix);
+    COPY_ATTR(exec_prefix);
+    COPY_ATTR(module_search_path);
+    COPY_ATTR(program_name);
+    COPY_ATTR(home);
+    config->isolated = config2->isolated;
+    config->site_import = config2->site_import;
+    COPY_ATTR(base_executable);
 
-    /* Calculate program_full_path, prefix, exec_prefix,
-       dll_path (Windows), and module_search_path */
-    status = _PyPathConfig_Calculate(&new_config, config);
-    if (_PyStatus_EXCEPTION(status)) {
-        goto error;
-    }
+#undef COPY_ATTR
 
-    /* Copy home and program_name from config */
-    if (copy_wstr(&new_config.home, config->home) < 0) {
-        status = _PyStatus_NO_MEMORY();
-        goto error;
-    }
-    if (copy_wstr(&new_config.program_name, config->program_name) < 0) {
-        status = _PyStatus_NO_MEMORY();
-        goto error;
-    }
-    if (config->base_executable) {
-        PyMem_RawFree(new_config.base_executable);
-        if (copy_wstr(&new_config.base_executable,
-                      config->base_executable) < 0) {
-            status = _PyStatus_NO_MEMORY();
-            goto error;
-        }
-    }
-
-    pathconfig_clear(pathconfig);
-    *pathconfig = new_config;
-
-    status = _PyStatus_OK();
-    goto done;
-
-error:
-    pathconfig_clear(&new_config);
-
-done:
-    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
-    return status;
+    return _PyStatus_OK();
 }
 
 
@@ -168,7 +139,7 @@
 }
 
 
-/* Set the global path configuration from config. */
+/* Initialize _Py_dll_path on Windows. Do nothing on other platforms. */
 PyStatus
 _PyPathConfig_Init(void)
 {
@@ -194,47 +165,39 @@
 
 
 static PyStatus
-pathconfig_global_init_from_config(const PyConfig *config)
+pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
 {
     PyStatus status;
     PyMemAllocatorEx old_alloc;
     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 
-    _PyPathConfig pathconfig = _PyPathConfig_INIT;
-
-    pathconfig.module_search_path = _PyWideStringList_Join(&config->module_search_paths, DELIM);
-    if (pathconfig.module_search_path == NULL) {
-        goto no_memory;
+    if (config->module_search_paths_set) {
+        pathconfig->module_search_path = _PyWideStringList_Join(&config->module_search_paths, DELIM);
+        if (pathconfig->module_search_path == NULL) {
+            goto no_memory;
+        }
     }
 
-    if (copy_wstr(&pathconfig.program_full_path, config->executable) < 0) {
-        goto no_memory;
-    }
-    if (copy_wstr(&pathconfig.prefix, config->prefix) < 0) {
-        goto no_memory;
-    }
-    if (copy_wstr(&pathconfig.exec_prefix, config->exec_prefix) < 0) {
-        goto no_memory;
-    }
-    if (copy_wstr(&pathconfig.program_name, config->program_name) < 0) {
-        goto no_memory;
-    }
-    if (copy_wstr(&pathconfig.home, config->home) < 0) {
-        goto no_memory;
-    }
-    if (copy_wstr(&pathconfig.base_executable, config->base_executable) < 0) {
-        goto no_memory;
-    }
+#define COPY_CONFIG(PATH_ATTR, CONFIG_ATTR) \
+        if (config->CONFIG_ATTR) { \
+            if (copy_wstr(&pathconfig->PATH_ATTR, config->CONFIG_ATTR) < 0) { \
+                goto no_memory; \
+            } \
+        }
 
-    pathconfig_clear(&_Py_path_config);
-    /* Steal new_config strings; don't clear new_config */
-    _Py_path_config = pathconfig;
+    COPY_CONFIG(base_executable, base_executable);
+    COPY_CONFIG(program_full_path, executable);
+    COPY_CONFIG(prefix, prefix);
+    COPY_CONFIG(exec_prefix, exec_prefix);
+    COPY_CONFIG(program_name, program_name);
+    COPY_CONFIG(home, home);
+
+#undef COPY_CONFIG
 
     status = _PyStatus_OK();
     goto done;
 
 no_memory:
-    pathconfig_clear(&pathconfig);
     status = _PyStatus_NO_MEMORY();
 
 done:
@@ -283,6 +246,61 @@
 }
 
 
+/* Calculate the path configuration:
+
+   - exec_prefix
+   - module_search_path
+   - prefix
+   - program_full_path
+
+   On Windows, more fields are calculated:
+
+   - base_executable
+   - isolated
+   - site_import
+
+   On other platforms, isolated and site_import are left unchanged, and
+   _PyConfig_InitPathConfig() copies executable to base_executable (if it's not
+   set).
+
+   Priority, highest to lowest:
+
+   - PyConfig
+   - _Py_path_config: set by Py_SetPath(), Py_SetPythonHome()
+     and Py_SetProgramName()
+   - _PyPathConfig_Calculate()
+*/
+static PyStatus
+pathconfig_calculate(_PyPathConfig *pathconfig, const PyConfig *config)
+{
+    PyStatus status;
+
+    PyMemAllocatorEx old_alloc;
+    _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+
+    status = pathconfig_copy(pathconfig, &_Py_path_config);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
+
+    status = pathconfig_set_from_config(pathconfig, config);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
+
+    if (_Py_path_config.module_search_path == NULL) {
+        status = _PyPathConfig_Calculate(pathconfig, config);
+    }
+    else {
+        /* Py_SetPath() has been called: avoid _PyPathConfig_Calculate() */
+    }
+
+done:
+    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+    return status;
+}
+
+
 static PyStatus
 config_calculate_pathconfig(PyConfig *config)
 {
@@ -291,42 +309,28 @@
 
     status = pathconfig_calculate(&pathconfig, config);
     if (_PyStatus_EXCEPTION(status)) {
-        goto error;
+        goto done;
     }
 
     if (!config->module_search_paths_set) {
         status = config_init_module_search_paths(config, &pathconfig);
         if (_PyStatus_EXCEPTION(status)) {
-            goto error;
+            goto done;
         }
     }
 
-    if (config->executable == NULL) {
-        if (copy_wstr(&config->executable,
-                      pathconfig.program_full_path) < 0) {
-            goto no_memory;
+#define COPY_ATTR(PATH_ATTR, CONFIG_ATTR) \
+        if (config->CONFIG_ATTR == NULL) { \
+            if (copy_wstr(&config->CONFIG_ATTR, pathconfig.PATH_ATTR) < 0) { \
+                goto no_memory; \
+            } \
         }
-    }
 
-    if (config->prefix == NULL) {
-        if (copy_wstr(&config->prefix, pathconfig.prefix) < 0) {
-            goto no_memory;
-        }
-    }
-
-    if (config->exec_prefix == NULL) {
-        if (copy_wstr(&config->exec_prefix,
-                      pathconfig.exec_prefix) < 0) {
-            goto no_memory;
-        }
-    }
-
-    if (config->base_executable == NULL) {
-        if (copy_wstr(&config->base_executable,
-                      pathconfig.base_executable) < 0) {
-            goto no_memory;
-        }
-    }
+    COPY_ATTR(program_full_path, executable);
+    COPY_ATTR(prefix, prefix);
+    COPY_ATTR(exec_prefix, exec_prefix);
+    COPY_ATTR(base_executable, base_executable);
+#undef COPY_ATTR
 
     if (pathconfig.isolated != -1) {
         config->isolated = pathconfig.isolated;
@@ -335,13 +339,13 @@
         config->site_import = pathconfig.site_import;
     }
 
-    pathconfig_clear(&pathconfig);
-    return _PyStatus_OK();
+    status = _PyStatus_OK();
+    goto done;
 
 no_memory:
     status = _PyStatus_NO_MEMORY();
 
-error:
+done:
     pathconfig_clear(&pathconfig);
     return status;
 }
@@ -386,39 +390,55 @@
 }
 
 
+static PyStatus
+pathconfig_global_read(_PyPathConfig *pathconfig)
+{
+    PyStatus status;
+    PyConfig config;
+    _PyConfig_InitCompatConfig(&config);
+
+    /* Call _PyConfig_InitPathConfig() */
+    status = PyConfig_Read(&config);
+    if (_PyStatus_EXCEPTION(status)) {
+        goto done;
+    }
+
+    status = pathconfig_set_from_config(pathconfig, &config);
+
+done:
+    PyConfig_Clear(&config);
+    return status;
+}
+
+
 static void
 pathconfig_global_init(void)
 {
+    PyStatus status;
+
     /* Initialize _Py_dll_path if needed */
-    PyStatus status = _PyPathConfig_Init();
+    status = _PyPathConfig_Init();
     if (_PyStatus_EXCEPTION(status)) {
         Py_ExitStatusException(status);
     }
 
-    if (_Py_path_config.module_search_path != NULL) {
-        /* Already initialized */
-        return;
+    if (_Py_path_config.module_search_path == NULL) {
+        status = pathconfig_global_read(&_Py_path_config);
+        if (_PyStatus_EXCEPTION(status)) {
+            Py_ExitStatusException(status);
+        }
+    }
+    else {
+        /* Global configuration already initialized */
     }
 
-    PyConfig config;
-    _PyConfig_InitCompatConfig(&config);
-
-    status = PyConfig_Read(&config);
-    if (_PyStatus_EXCEPTION(status)) {
-        goto error;
-    }
-
-    status = pathconfig_global_init_from_config(&config);
-    if (_PyStatus_EXCEPTION(status)) {
-        goto error;
-    }
-
-    PyConfig_Clear(&config);
-    return;
-
-error:
-    PyConfig_Clear(&config);
-    Py_ExitStatusException(status);
+    assert(_Py_path_config.program_full_path != NULL);
+    assert(_Py_path_config.prefix != NULL);
+    assert(_Py_path_config.exec_prefix != NULL);
+    assert(_Py_path_config.module_search_path != NULL);
+    assert(_Py_path_config.program_name != NULL);
+    /* home can be NULL */
+    assert(_Py_path_config.base_executable != NULL);
 }
 
 
@@ -435,30 +455,27 @@
     PyMemAllocatorEx old_alloc;
     _PyMem_SetDefaultAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 
-    _PyPathConfig new_config;
-    new_config.program_full_path = _PyMem_RawWcsdup(Py_GetProgramName());
-    int alloc_error = (new_config.program_full_path == NULL);
-    new_config.prefix = _PyMem_RawWcsdup(L"");
-    alloc_error |= (new_config.prefix == NULL);
-    new_config.exec_prefix = _PyMem_RawWcsdup(L"");
-    alloc_error |= (new_config.exec_prefix == NULL);
-    new_config.module_search_path = _PyMem_RawWcsdup(path);
-    alloc_error |= (new_config.module_search_path == NULL);
+    /* Getting the program name calls pathconfig_global_init() */
+    wchar_t *program_name = _PyMem_RawWcsdup(Py_GetProgramName());
 
-    /* steal the home and program_name values (to leave them unchanged) */
-    new_config.home = _Py_path_config.home;
-    _Py_path_config.home = NULL;
-    new_config.program_name = _Py_path_config.program_name;
-    _Py_path_config.program_name = NULL;
-    new_config.base_executable = _Py_path_config.base_executable;
-    _Py_path_config.base_executable = NULL;
+    PyMem_RawFree(_Py_path_config.program_full_path);
+    PyMem_RawFree(_Py_path_config.prefix);
+    PyMem_RawFree(_Py_path_config.exec_prefix);
+    PyMem_RawFree(_Py_path_config.module_search_path);
 
-    pathconfig_clear(&_Py_path_config);
-    _Py_path_config = new_config;
+    /* Copy program_name to program_full_path  */
+    _Py_path_config.program_full_path = program_name;
+    _Py_path_config.prefix = _PyMem_RawWcsdup(L"");
+    _Py_path_config.exec_prefix = _PyMem_RawWcsdup(L"");
+    _Py_path_config.module_search_path = _PyMem_RawWcsdup(path);
 
     PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
 
-    if (alloc_error) {
+    if (_Py_path_config.program_full_path == NULL
+        || _Py_path_config.prefix == NULL
+        || _Py_path_config.exec_prefix == NULL
+        || _Py_path_config.module_search_path == NULL)
+    {
         Py_FatalError("Py_SetPath() failed: out of memory");
     }
 }