[Python-checkins] bpo-32030: Split Py_Main() into subfunctions (#4399)

Victor Stinner webhook-mailer at python.org
Wed Nov 15 18:48:14 EST 2017


https://github.com/python/cpython/commit/f7e5b56c37eb859e225e886c79c5d742c567ee95
commit: f7e5b56c37eb859e225e886c79c5d742c567ee95
branch: master
author: Victor Stinner <victor.stinner at gmail.com>
committer: GitHub <noreply at github.com>
date: 2017-11-15T15:48:08-08:00
summary:

bpo-32030: Split Py_Main() into subfunctions (#4399)

* Don't use "Python runtime" anymore to parse command line options or
  to get environment variables: pymain_init() is now a strict
  separation.
* Use an error message rather than "crashing" directly with
  Py_FatalError(). Limit the number of calls to Py_FatalError(). It
  prepares the code to handle errors more nicely later.
* Warnings options (-W, PYTHONWARNINGS) and "XOptions" (-X) are now
  only added to the sys module once Python core is properly
  initialized.
* _PyMain is now the well identified owner of some important strings
  like: warnings options, XOptions, and the "program name". The
  program name string is now properly freed at exit.
  pymain_free() is now responsible to free the "command" string.
* Rename most methods in Modules/main.c to use a "pymain_" prefix to
  avoid conflits and ease debug.
* Replace _Py_CommandLineDetails_INIT with memset(0)
* Reorder a lot of code to fix the initialization ordering. For
  example, initializing standard streams now comes before parsing
  PYTHONWARNINGS.
* Py_Main() now handles errors when adding warnings options and
  XOptions.
* Add _PyMem_GetDefaultRawAllocator() private function.
* Cleanup _PyMem_Initialize(): remove useless global constants: move
  them into _PyMem_Initialize().
* Call _PyRuntime_Initialize() as soon as possible:
  _PyRuntime_Initialize() now returns an error message on failure.
* Add _PyInitError structure and following macros:

  * _Py_INIT_OK()
  * _Py_INIT_ERR(msg)
  * _Py_INIT_USER_ERR(msg): "user" error, don't abort() in that case
  * _Py_INIT_FAILED(err)

files:
M Include/import.h
M Include/internal/pystate.h
M Include/pylifecycle.h
M Include/pymem.h
M Include/sysmodule.h
M Modules/faulthandler.c
M Modules/main.c
M Objects/obmalloc.c
M PCbuild/python.vcxproj
M Parser/pgenmain.c
M Programs/_freeze_importlib.c
M Programs/python.c
M Python/bootstrap_hash.c
M Python/frozenmain.c
M Python/import.c
M Python/pylifecycle.c
M Python/pystate.c
M Python/sysmodule.c

diff --git a/Include/import.h b/Include/import.h
index c30f3ea3394..26c4b1f18ef 100644
--- a/Include/import.h
+++ b/Include/import.h
@@ -8,7 +8,7 @@ extern "C" {
 #endif
 
 #ifndef Py_LIMITED_API
-PyAPI_FUNC(void) _PyImportZip_Init(void);
+PyAPI_FUNC(_PyInitError) _PyImportZip_Init(void);
 
 PyMODINIT_FUNC PyInit_imp(void);
 #endif /* !Py_LIMITED_API */
diff --git a/Include/internal/pystate.h b/Include/internal/pystate.h
index 210917bb325..516f829ac32 100644
--- a/Include/internal/pystate.h
+++ b/Include/internal/pystate.h
@@ -74,10 +74,16 @@ typedef struct pyruntimestate {
     // XXX Consolidate globals found via the check-c-globals script.
 } _PyRuntimeState;
 
+#define _PyRuntimeState_INIT {.initialized = 0, .core_initialized = 0}
+
 PyAPI_DATA(_PyRuntimeState) _PyRuntime;
-PyAPI_FUNC(void) _PyRuntimeState_Init(_PyRuntimeState *);
+PyAPI_FUNC(_PyInitError) _PyRuntimeState_Init(_PyRuntimeState *);
 PyAPI_FUNC(void) _PyRuntimeState_Fini(_PyRuntimeState *);
 
+/* Initialize _PyRuntimeState.
+   Return NULL on success, or return an error message on failure. */
+PyAPI_FUNC(_PyInitError) _PyRuntime_Initialize(void);
+
 #define _Py_CURRENTLY_FINALIZING(tstate) \
     (_PyRuntime.finalizing == tstate)
 
diff --git a/Include/pylifecycle.h b/Include/pylifecycle.h
index f7286f37647..a75b77cc733 100644
--- a/Include/pylifecycle.h
+++ b/Include/pylifecycle.h
@@ -20,18 +20,44 @@ PyAPI_FUNC(wchar_t *) Py_GetPythonHome(void);
 PyAPI_FUNC(int) Py_SetStandardStreamEncoding(const char *encoding,
                                              const char *errors);
 
+typedef struct {
+    const char *prefix;
+    const char *msg;
+    int user_err;
+} _PyInitError;
+
+/* Almost all errors causing Python initialization to fail */
+#ifdef _MSC_VER
+   /* Visual Studio 2015 doesn't implement C99 __func__ in C */
+#  define _Py_INIT_GET_FUNC() __FUNCTION__
+#else
+#  define _Py_INIT_GET_FUNC() __func__
+#endif
+
+#define _Py_INIT_OK() \
+    (_PyInitError){.prefix = NULL, .msg = NULL, .user_err = 0}
+#define _Py_INIT_ERR(MSG) \
+    (_PyInitError){.prefix = _Py_INIT_GET_FUNC(), .msg = (MSG), .user_err = 0}
+/* Error that can be fixed by the user like invalid input parameter.
+   Don't abort() the process on such error. */
+#define _Py_INIT_USER_ERR(MSG) \
+    (_PyInitError){.prefix = _Py_INIT_GET_FUNC(), .msg = (MSG), .user_err = 1}
+#define _Py_INIT_FAILED(err) \
+    (err.msg != NULL)
+
 /* PEP 432 Multi-phase initialization API (Private while provisional!) */
-PyAPI_FUNC(void) _Py_InitializeCore(const _PyCoreConfig *);
+PyAPI_FUNC(_PyInitError) _Py_InitializeCore(const _PyCoreConfig *);
 PyAPI_FUNC(int) _Py_IsCoreInitialized(void);
-PyAPI_FUNC(int) _Py_ReadMainInterpreterConfig(_PyMainInterpreterConfig *);
-PyAPI_FUNC(int) _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *);
+PyAPI_FUNC(_PyInitError) _Py_ReadMainInterpreterConfig(_PyMainInterpreterConfig *);
+PyAPI_FUNC(_PyInitError) _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *);
 #endif
 
 /* Initialization and finalization */
 PyAPI_FUNC(void) Py_Initialize(void);
 PyAPI_FUNC(void) Py_InitializeEx(int);
 #ifndef Py_LIMITED_API
-PyAPI_FUNC(void) _Py_InitializeEx_Private(int, int);
+PyAPI_FUNC(_PyInitError) _Py_InitializeEx_Private(int, int);
+PyAPI_FUNC(void) _Py_FatalInitError(_PyInitError err) _Py_NO_RETURN;
 #endif
 PyAPI_FUNC(void) Py_Finalize(void);
 PyAPI_FUNC(int) Py_FinalizeEx(void);
@@ -50,7 +76,7 @@ PyAPI_FUNC(void) _Py_PyAtExit(void (*func)(void));
 #endif
 PyAPI_FUNC(int) Py_AtExit(void (*func)(void));
 
-PyAPI_FUNC(void) Py_Exit(int);
+PyAPI_FUNC(void) Py_Exit(int) _Py_NO_RETURN;
 
 /* Restore signals that the interpreter has called SIG_IGN on to SIG_DFL. */
 #ifndef Py_LIMITED_API
@@ -86,15 +112,15 @@ PyAPI_FUNC(const char *) _Py_gitversion(void);
 /* Internal -- various one-time initializations */
 #ifndef Py_LIMITED_API
 PyAPI_FUNC(PyObject *) _PyBuiltin_Init(void);
-PyAPI_FUNC(PyObject *) _PySys_BeginInit(void);
+PyAPI_FUNC(_PyInitError) _PySys_BeginInit(PyObject **sysmod);
 PyAPI_FUNC(int) _PySys_EndInit(PyObject *sysdict);
-PyAPI_FUNC(void) _PyImport_Init(void);
+PyAPI_FUNC(_PyInitError) _PyImport_Init(void);
 PyAPI_FUNC(void) _PyExc_Init(PyObject * bltinmod);
-PyAPI_FUNC(void) _PyImportHooks_Init(void);
+PyAPI_FUNC(_PyInitError) _PyImportHooks_Init(void);
 PyAPI_FUNC(int) _PyFrame_Init(void);
 PyAPI_FUNC(int) _PyFloat_Init(void);
 PyAPI_FUNC(int) PyByteArray_Init(void);
-PyAPI_FUNC(void) _Py_HashRandomization_Init(_PyCoreConfig *core_config);
+PyAPI_FUNC(_PyInitError) _Py_HashRandomization_Init(_PyCoreConfig *core_config);
 #endif
 
 /* Various internal finalizers */
diff --git a/Include/pymem.h b/Include/pymem.h
index 2670dcdfa92..928851a3d70 100644
--- a/Include/pymem.h
+++ b/Include/pymem.h
@@ -223,6 +223,10 @@ PyAPI_FUNC(void) PyMem_SetAllocator(PyMemAllocatorDomain domain,
 PyAPI_FUNC(void) PyMem_SetupDebugHooks(void);
 #endif
 
+#ifdef Py_BUILD_CORE
+PyAPI_FUNC(void) _PyMem_GetDefaultRawAllocator(PyMemAllocatorEx *alloc);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Include/sysmodule.h b/Include/sysmodule.h
index c5547ff6742..719ecfcf61f 100644
--- a/Include/sysmodule.h
+++ b/Include/sysmodule.h
@@ -37,6 +37,11 @@ PyAPI_FUNC(PyObject *) PySys_GetXOptions(void);
 PyAPI_FUNC(size_t) _PySys_GetSizeOf(PyObject *);
 #endif
 
+#ifdef Py_BUILD_CORE
+PyAPI_FUNC(int) _PySys_AddXOptionWithError(const wchar_t *s);
+PyAPI_FUNC(int) _PySys_AddWarnOptionWithError(PyObject *option);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/Modules/faulthandler.c b/Modules/faulthandler.c
index 33edc0547e6..b6d5aee9b13 100644
--- a/Modules/faulthandler.c
+++ b/Modules/faulthandler.c
@@ -1281,47 +1281,54 @@ PyInit_faulthandler(void)
     return m;
 }
 
-/* Call faulthandler.enable() if the PYTHONFAULTHANDLER environment variable
-   is defined, or if sys._xoptions has a 'faulthandler' key. */
-
 static int
-faulthandler_env_options(void)
+faulthandler_init_enable(void)
 {
-    PyObject *xoptions, *key, *module, *res;
-    char *p;
+    PyObject *module = PyImport_ImportModule("faulthandler");
+    if (module == NULL) {
+        return -1;
+    }
 
-    if (!((p = Py_GETENV("PYTHONFAULTHANDLER")) && *p != '\0')) {
-        /* PYTHONFAULTHANDLER environment variable is missing
-           or an empty string */
-        int has_key;
+    PyObject *res = _PyObject_CallMethodId(module, &PyId_enable, NULL);
+    Py_DECREF(module);
+    if (res == NULL) {
+        return -1;
+    }
+    Py_DECREF(res);
 
-        xoptions = PySys_GetXOptions();
-        if (xoptions == NULL)
-            return -1;
+    return 0;
+}
 
-        key = PyUnicode_FromString("faulthandler");
-        if (key == NULL)
-            return -1;
+/* Call faulthandler.enable() if the PYTHONFAULTHANDLER environment variable
+   is defined, or if sys._xoptions has a 'faulthandler' key. */
 
-        has_key = PyDict_Contains(xoptions, key);
-        Py_DECREF(key);
-        if (has_key <= 0)
-            return has_key;
+static int
+faulthandler_init_parse(void)
+{
+    char *p = Py_GETENV("PYTHONFAULTHANDLER");
+    if (p && *p != '\0') {
+        return 1;
     }
 
-    module = PyImport_ImportModule("faulthandler");
-    if (module == NULL) {
+    /* PYTHONFAULTHANDLER environment variable is missing
+       or an empty string */
+    PyObject *xoptions = PySys_GetXOptions();
+    if (xoptions == NULL) {
         return -1;
     }
-    res = _PyObject_CallMethodId(module, &PyId_enable, NULL);
-    Py_DECREF(module);
-    if (res == NULL)
+
+    PyObject *key = PyUnicode_FromString("faulthandler");
+    if (key == NULL) {
         return -1;
-    Py_DECREF(res);
-    return 0;
+    }
+
+    int has_key = PyDict_Contains(xoptions, key);
+    Py_DECREF(key);
+    return has_key;
 }
 
-int _PyFaulthandler_Init(void)
+_PyInitError
+_PyFaulthandler_Init(void)
 {
 #ifdef HAVE_SIGALTSTACK
     int err;
@@ -1345,14 +1352,22 @@ int _PyFaulthandler_Init(void)
     thread.cancel_event = PyThread_allocate_lock();
     thread.running = PyThread_allocate_lock();
     if (!thread.cancel_event || !thread.running) {
-        PyErr_SetString(PyExc_RuntimeError,
-                        "could not allocate locks for faulthandler");
-        return -1;
+        return _Py_INIT_ERR("failed to allocate locks for faulthandler");
     }
     PyThread_acquire_lock(thread.cancel_event, 1);
 #endif
 
-    return faulthandler_env_options();
+    int enable = faulthandler_init_parse();
+    if (enable < 0) {
+        return _Py_INIT_ERR("failed to parse faulthandler env var and cmdline");
+    }
+
+    if (enable) {
+        if (faulthandler_init_enable() < 0) {
+            return _Py_INIT_ERR("failed to enable faulthandler");
+        }
+    }
+    return _Py_INIT_OK();
 }
 
 void _PyFaulthandler_Fini(void)
diff --git a/Modules/main.c b/Modules/main.c
index 54abbcc5474..6391ba78d83 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -2,6 +2,7 @@
 
 #include "Python.h"
 #include "osdefs.h"
+#include "internal/pystate.h"
 
 #include <locale.h>
 
@@ -37,7 +38,7 @@ extern "C" {
 
 /* For Py_GetArgcArgv(); set by main() */
 static wchar_t **orig_argv;
-static int  orig_argc;
+static int orig_argc;
 
 /* command line options */
 #define BASE_OPTS L"bBc:dEhiIJm:OqRsStuvVW:xX:?"
@@ -109,13 +110,13 @@ static const char usage_6[] =
 "   coercion behavior. Use PYTHONCOERCECLOCALE=warn to request display of\n"
 "   locale coercion and locale compatibility warnings on stderr.\n";
 
-static int
-usage(int exitcode, const wchar_t* program)
+static void
+usage(int error, const wchar_t* program)
 {
-    FILE *f = exitcode ? stderr : stdout;
+    FILE *f = error ? stderr : stdout;
 
     fprintf(f, usage_line, program);
-    if (exitcode)
+    if (error)
         fprintf(f, "Try `python -h' for more information.\n");
     else {
         fputs(usage_1, f);
@@ -125,50 +126,57 @@ usage(int exitcode, const wchar_t* program)
         fprintf(f, usage_5, (wint_t)DELIM, PYTHONHOMEHELP);
         fputs(usage_6, f);
     }
-    return exitcode;
 }
 
-static void RunStartupFile(PyCompilerFlags *cf)
+static void
+pymain_run_statup(PyCompilerFlags *cf)
 {
     char *startup = Py_GETENV("PYTHONSTARTUP");
-    if (startup != NULL && startup[0] != '\0') {
-        FILE *fp = _Py_fopen(startup, "r");
-        if (fp != NULL) {
-            (void) PyRun_SimpleFileExFlags(fp, startup, 0, cf);
-            PyErr_Clear();
-            fclose(fp);
-        } else {
-            int save_errno;
-
-            save_errno = errno;
-            PySys_WriteStderr("Could not open PYTHONSTARTUP\n");
-            errno = save_errno;
-            PyErr_SetFromErrnoWithFilename(PyExc_OSError,
-                            startup);
-            PyErr_Print();
-            PyErr_Clear();
-        }
+    if (startup == NULL || startup[0] == '\0') {
+        return;
+    }
+
+    FILE *fp = _Py_fopen(startup, "r");
+    if (fp == NULL) {
+        int save_errno = errno;
+        PySys_WriteStderr("Could not open PYTHONSTARTUP\n");
+        errno = save_errno;
+
+        PyErr_SetFromErrnoWithFilename(PyExc_OSError,
+                        startup);
+        PyErr_Print();
+        PyErr_Clear();
+        return;
     }
+
+    (void) PyRun_SimpleFileExFlags(fp, startup, 0, cf);
+    PyErr_Clear();
+    fclose(fp);
 }
 
-static void RunInteractiveHook(void)
+static void
+pymain_run_interactive_hook(void)
 {
     PyObject *sys, *hook, *result;
     sys = PyImport_ImportModule("sys");
-    if (sys == NULL)
+    if (sys == NULL) {
         goto error;
+    }
+
     hook = PyObject_GetAttrString(sys, "__interactivehook__");
     Py_DECREF(sys);
-    if (hook == NULL)
+    if (hook == NULL) {
         PyErr_Clear();
-    else {
-        result = _PyObject_CallNoArg(hook);
-        Py_DECREF(hook);
-        if (result == NULL)
-            goto error;
-        else
-            Py_DECREF(result);
+        return;
+    }
+
+    result = _PyObject_CallNoArg(hook);
+    Py_DECREF(hook);
+    if (result == NULL) {
+        goto error;
     }
+    Py_DECREF(result);
+
     return;
 
 error:
@@ -178,7 +186,8 @@ static void RunInteractiveHook(void)
 }
 
 
-static int RunModule(wchar_t *modname, int set_argv0)
+static int
+pymain_run_module(wchar_t *modname, int set_argv0)
 {
     PyObject *module, *runpy, *runmodule, *runargs, *result;
     runpy = PyImport_ImportModule("runpy");
@@ -228,23 +237,26 @@ static int RunModule(wchar_t *modname, int set_argv0)
 }
 
 static PyObject *
-AsImportPathEntry(wchar_t *filename)
+pymain_get_importer(wchar_t *filename)
 {
     PyObject *sys_path0 = NULL, *importer;
 
     sys_path0 = PyUnicode_FromWideChar(filename, wcslen(filename));
-    if (sys_path0 == NULL)
+    if (sys_path0 == NULL) {
         goto error;
+    }
 
     importer = PyImport_GetImporter(sys_path0);
-    if (importer == NULL)
+    if (importer == NULL) {
         goto error;
+    }
 
     if (importer == Py_None) {
         Py_DECREF(sys_path0);
         Py_DECREF(importer);
         return NULL;
     }
+
     Py_DECREF(importer);
     return sys_path0;
 
@@ -258,49 +270,25 @@ AsImportPathEntry(wchar_t *filename)
 
 
 static int
-RunMainFromImporter(PyObject *sys_path0)
-{
-    PyObject *sys_path;
-    int sts;
-
-    /* Assume sys_path0 has already been checked by AsImportPathEntry,
-     * so put it in sys.path[0] and import __main__ */
-    sys_path = PySys_GetObject("path");
-    if (sys_path == NULL) {
-        PyErr_SetString(PyExc_RuntimeError, "unable to get sys.path");
-        goto error;
-    }
-    sts = PyList_Insert(sys_path, 0, sys_path0);
-    if (sts) {
-        sys_path0 = NULL;
-        goto error;
-    }
-
-    sts = RunModule(L"__main__", 0);
-    return sts != 0;
-
-error:
-    Py_XDECREF(sys_path0);
-    PyErr_Print();
-    return 1;
-}
-
-static int
-run_command(wchar_t *command, PyCompilerFlags *cf)
+pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
 {
     PyObject *unicode, *bytes;
     int ret;
 
     unicode = PyUnicode_FromWideChar(command, -1);
-    if (unicode == NULL)
+    if (unicode == NULL) {
         goto error;
+    }
+
     bytes = PyUnicode_AsUTF8String(unicode);
     Py_DECREF(unicode);
-    if (bytes == NULL)
+    if (bytes == NULL) {
         goto error;
+    }
+
     ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf);
     Py_DECREF(bytes);
-    return ret != 0;
+    return (ret != 0);
 
 error:
     PySys_WriteStderr("Unable to decode the command from the command line:\n");
@@ -308,8 +296,9 @@ run_command(wchar_t *command, PyCompilerFlags *cf)
     return 1;
 }
 
+
 static int
-run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
+pymain_run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
 {
     PyObject *unicode, *bytes = NULL;
     const char *filename_str;
@@ -327,15 +316,17 @@ run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
             bytes = PyUnicode_EncodeFSDefault(unicode);
             Py_DECREF(unicode);
         }
-        if (bytes != NULL)
+        if (bytes != NULL) {
             filename_str = PyBytes_AsString(bytes);
+        }
         else {
             PyErr_Clear();
             filename_str = "<encoding error>";
         }
     }
-    else
+    else {
         filename_str = "<stdin>";
+    }
 
     run = PyRun_AnyFileExFlags(fp, filename_str, filename != NULL, p_cf);
     Py_XDECREF(bytes);
@@ -348,10 +339,15 @@ run_file(FILE *fp, const wchar_t *filename, PyCompilerFlags *p_cf)
 /*TODO: Add arg processing to PEP 432 as a new configuration setup API
  */
 typedef struct {
+    size_t len;
+    wchar_t **options;
+} _Py_OptList;
+
+typedef struct {
     wchar_t *filename;           /* Trailing arg without -c or -m */
     wchar_t *command;            /* -c argument */
     wchar_t *module;             /* -m argument */
-    PyObject *warning_options;   /* -W options */
+    _Py_OptList warning_options; /* -W options */
     PyObject *extra_options;     /* -X options */
     int print_help;              /* -h, -? options */
     int print_version;           /* -V option */
@@ -368,36 +364,189 @@ typedef struct {
     int verbosity;               /* Py_VerboseFlag */
     int quiet_flag;              /* Py_QuietFlag */
     int skip_first_line;         /* -x option */
+    _Py_OptList xoptions;        /* -X options */
 } _Py_CommandLineDetails;
 
-#define _Py_CommandLineDetails_INIT \
-            {NULL, NULL, NULL, NULL, NULL, \
-             0, 0, 0, 0, 0, 0, 0, 0, \
-             0, 0, 0, 0, 0, 0, 0}
+/* Structure used by Py_Main() to pass data to subfunctions */
+typedef struct {
+    /* Exit status ("exit code") */
+    int status;
+    PyCompilerFlags cf;
+    /* non-zero is stdin is a TTY or if -i option is used */
+    int stdin_is_interactive;
+    _PyCoreConfig core_config;
+    _Py_CommandLineDetails cmdline;
+    PyObject *main_importer_path;
+    /* non-zero if filename, command (-c) or module (-m) is set
+       on the command line */
+    int run_code;
+    wchar_t *program_name;
+    /* Error message if a function failed */
+    _PyInitError err;
+    /* PYTHONWARNINGS env var */
+    _Py_OptList env_warning_options;
+    int argc;
+    wchar_t **argv;
+} _PyMain;
+
+/* .cmdline is initialized to zeros */
+#define _PyMain_INIT \
+    {.status = 0, \
+     .cf = {.cf_flags = 0}, \
+     .core_config = _PyCoreConfig_INIT, \
+     .main_importer_path = NULL, \
+     .run_code = -1, \
+     .program_name = NULL, \
+     .err = _Py_INIT_OK(), \
+     .env_warning_options = {0, NULL}}
+
+
+#define INIT_NO_MEMORY() _Py_INIT_ERR("memory allocation failed")
+
+
+static void
+pymain_optlist_clear(_Py_OptList *list)
+{
+    for (size_t i=0; i < list->len; i++) {
+        PyMem_RawFree(list->options[i]);
+    }
+    PyMem_RawFree(list->options);
+    list->len = 0;
+    list->options = NULL;
+}
+
+static void
+pymain_free_impl(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+    pymain_optlist_clear(&cmdline->warning_options);
+    pymain_optlist_clear(&cmdline->xoptions);
+    PyMem_RawFree(cmdline->command);
+
+    pymain_optlist_clear(&pymain->env_warning_options);
+    Py_CLEAR(pymain->main_importer_path);
+    PyMem_RawFree(pymain->program_name);
+
+#ifdef __INSURE__
+    /* Insure++ is a memory analysis tool that aids in discovering
+     * memory leaks and other memory problems.  On Python exit, the
+     * interned string dictionaries are flagged as being in use at exit
+     * (which it is).  Under normal circumstances, this is fine because
+     * the memory will be automatically reclaimed by the system.  Under
+     * memory debugging, it's a huge source of useless noise, so we
+     * trade off slower shutdown for less distraction in the memory
+     * reports.  -baw
+     */
+    _Py_ReleaseInternedUnicodeStrings();
+#endif /* __INSURE__ */
+}
+
+static void
+pymain_free(_PyMain *pymain)
+{
+    /* Call pymain_free() with the memory allocator used by pymain_init() */
+    PyMemAllocatorEx old_alloc, raw_alloc;
+    PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+    _PyMem_GetDefaultRawAllocator(&raw_alloc);
+    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &raw_alloc);
+
+    pymain_free_impl(pymain);
+
+    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+}
+
+
+static int
+pymain_run_main_from_importer(_PyMain *pymain)
+{
+    PyObject *sys_path0 = pymain->main_importer_path;
+    PyObject *sys_path;
+    int sts;
+
+    /* Assume sys_path0 has already been checked by pymain_get_importer(),
+     * so put it in sys.path[0] and import __main__ */
+    sys_path = PySys_GetObject("path");
+    if (sys_path == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "unable to get sys.path");
+        goto error;
+    }
+
+    sts = PyList_Insert(sys_path, 0, sys_path0);
+    if (sts) {
+        sys_path0 = NULL;
+        goto error;
+    }
+
+    sts = pymain_run_module(L"__main__", 0);
+    return sts != 0;
+
+error:
+    Py_CLEAR(pymain->main_importer_path);
+    PyErr_Print();
+    return 1;
+}
+
+
+static wchar_t*
+pymain_strdup(wchar_t *str)
+{
+    size_t len = wcslen(str) + 1;  /* +1 for NUL character */
+    wchar_t *str2 = PyMem_RawMalloc(sizeof(wchar_t) * len);
+    if (str2 == NULL) {
+        return NULL;
+    }
+    memcpy(str2, str, len * sizeof(wchar_t));
+    return str2;
+}
+
+
+static int
+pymain_optlist_append(_Py_OptList *list, wchar_t *str)
+{
+    wchar_t *str2 = pymain_strdup(str);
+    if (str2 == NULL) {
+        return -1;
+    }
+
+    size_t size = (list->len + 1) * sizeof(list[0]);
+    wchar_t **options2 = (wchar_t **)PyMem_RawRealloc(list->options, size);
+    if (options2 == NULL) {
+        PyMem_RawFree(str2);
+        return -1;
+    }
+    options2[list->len] = str2;
+    list->options = options2;
+    list->len++;
+    return 0;
+}
+
 
+/* Parse the command line arguments
+   Return 0 on success.
+   Return 1 if parsing failed.
+   Set pymain->err and return -1 on other errors. */
 static int
-read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
+pymain_parse_cmdline(_PyMain *pymain)
 {
-    PyObject *warning_option = NULL;
-    wchar_t *command = NULL;
-    wchar_t *module = NULL;
-    int c;
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
 
     _PyOS_ResetGetOpt();
+    do {
+        int c = _PyOS_GetOpt(pymain->argc, pymain->argv, PROGRAM_OPTS);
+        if (c == EOF) {
+            break;
+        }
 
-    while ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {
         if (c == 'c') {
-            size_t len;
             /* -c is the last option; following arguments
                that look like options are left for the
                command to interpret. */
-
-            len = wcslen(_PyOS_optarg) + 1 + 1;
-            command = (wchar_t *)PyMem_RawMalloc(sizeof(wchar_t) * len);
-            if (command == NULL)
-                Py_FatalError(
-                   "not enough memory to copy -c argument");
-            wcscpy(command, _PyOS_optarg);
+            size_t len = wcslen(_PyOS_optarg) + 1 + 1;
+            wchar_t *command = PyMem_RawMalloc(sizeof(wchar_t) * len);
+            if (command == NULL) {
+                goto out_of_memory;
+            }
+            memcpy(command, _PyOS_optarg, len * sizeof(wchar_t));
             command[len - 2] = '\n';
             command[len - 1] = 0;
             cmdline->command = command;
@@ -408,8 +557,7 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
             /* -m is the last option; following arguments
                that look like options are left for the
                module to interpret. */
-            module = _PyOS_optarg;
-            cmdline->module = module;
+            cmdline->module = _PyOS_optarg;
             break;
         }
 
@@ -428,6 +576,7 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
             break;
 
         case 'I':
+            pymain->core_config.ignore_environment++;
             cmdline->isolated++;
             cmdline->no_user_site_directory++;
             break;
@@ -451,7 +600,7 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
             break;
 
         case 'E':
-            /* Handled prior to core initialization */
+            pymain->core_config.ignore_environment++;
             break;
 
         case 't':
@@ -480,21 +629,17 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
             break;
 
         case 'W':
-            if (cmdline->warning_options == NULL)
-                cmdline->warning_options = PyList_New(0);
-            if (cmdline->warning_options == NULL)
-                Py_FatalError("failure in handling of -W argument");
-            warning_option = PyUnicode_FromWideChar(_PyOS_optarg, -1);
-            if (warning_option == NULL)
-                Py_FatalError("failure in handling of -W argument");
-            if (PyList_Append(cmdline->warning_options, warning_option) == -1)
-                Py_FatalError("failure in handling of -W argument");
-            Py_DECREF(warning_option);
+            if (pymain_optlist_append(&cmdline->warning_options,
+                                      _PyOS_optarg) < 0) {
+                goto out_of_memory;
+            }
             break;
 
         case 'X':
-            /* TODO: Delay addition of X options to sys module */
-            PySys_AddXOption(_PyOS_optarg);
+            if (pymain_optlist_append(&cmdline->xoptions,
+                                      _PyOS_optarg) < 0) {
+                goto out_of_memory;
+            }
             break;
 
         case 'q':
@@ -508,20 +653,26 @@ read_command_line(int argc, wchar_t **argv, _Py_CommandLineDetails *cmdline)
         /* This space reserved for other options */
 
         default:
-            return -1;
-            /*NOTREACHED*/
-
+            /* unknown argument: parsing failed */
+            return 1;
         }
-    }
+    } while (1);
 
-    if (command == NULL && module == NULL && _PyOS_optind < argc &&
-        wcscmp(argv[_PyOS_optind], L"-") != 0)
+    if (cmdline->command == NULL && cmdline->module == NULL
+        && _PyOS_optind < pymain->argc
+        && wcscmp(pymain->argv[_PyOS_optind], L"-") != 0)
     {
-        cmdline->filename = argv[_PyOS_optind];
+        cmdline->filename = pymain->argv[_PyOS_optind];
     }
+
     return 0;
+
+out_of_memory:
+    pymain->err = INIT_NO_MEMORY();
+    return -1;
 }
 
+
 static void
 maybe_set_flag(int *flag, int value)
 {
@@ -534,146 +685,142 @@ maybe_set_flag(int *flag, int value)
     }
 }
 
+
 static int
-apply_command_line_and_environment(_Py_CommandLineDetails *cmdline)
+pymain_add_xoptions(_PyMain *pymain)
 {
-    maybe_set_flag(&Py_BytesWarningFlag, cmdline->bytes_warning);
-    maybe_set_flag(&Py_DebugFlag, cmdline->debug);
-    maybe_set_flag(&Py_InspectFlag, cmdline->inspect);
-    maybe_set_flag(&Py_InteractiveFlag, cmdline->interactive);
-    maybe_set_flag(&Py_IsolatedFlag, cmdline->isolated);
-    maybe_set_flag(&Py_OptimizeFlag, cmdline->optimization_level);
-    maybe_set_flag(&Py_DontWriteBytecodeFlag, cmdline->dont_write_bytecode);
-    maybe_set_flag(&Py_NoUserSiteDirectory, cmdline->no_user_site_directory);
-    maybe_set_flag(&Py_NoSiteFlag, cmdline->no_site_import);
-    maybe_set_flag(&Py_UnbufferedStdioFlag, cmdline->use_unbuffered_io);
-    maybe_set_flag(&Py_VerboseFlag, cmdline->verbosity);
-    maybe_set_flag(&Py_QuietFlag, cmdline->quiet_flag);
-
-    /* TODO: Apply PYTHONWARNINGS & -W options to sys module here */
-    /* TODO: Apply -X options to sys module here */
+    _Py_OptList *options = &pymain->cmdline.xoptions;
+    for (size_t i=0; i < options->len; i++) {
+        wchar_t *option = options->options[i];
+        if (_PySys_AddXOptionWithError(option) < 0) {
+            pymain->err = INIT_NO_MEMORY();
+            return -1;
+        }
+    }
     return 0;
 }
 
-int
-Py_Main(int argc, wchar_t **argv)
-{
-    int c;
-    int sts;
-    FILE *fp = stdin;
-    char *p;
-#ifdef MS_WINDOWS
-    wchar_t *wp;
-#endif
-    int stdin_is_interactive = 0;
-    _Py_CommandLineDetails cmdline = _Py_CommandLineDetails_INIT;
-    _PyCoreConfig core_config = _PyCoreConfig_INIT;
-    PyCompilerFlags cf;
-    PyObject *main_importer_path = NULL;
-
-    cf.cf_flags = 0;
 
-    orig_argc = argc;           /* For Py_GetArgcArgv() */
-    orig_argv = argv;
-
-    /* Hash randomization needed early for all string operations
-       (including -W and -X options). */
-    _PyOS_opterr = 0;  /* prevent printing the error in 1st pass */
-    while ((c = _PyOS_GetOpt(argc, argv, PROGRAM_OPTS)) != EOF) {
-        if (c == 'm' || c == 'c') {
-            /* -c / -m is the last option: following arguments are
-               not interpreter options. */
-            break;
+static int
+pymain_add_warnings_optlist(_Py_OptList *warnings)
+{
+    for (size_t i = 0; i < warnings->len; i++) {
+        PyObject *option = PyUnicode_FromWideChar(warnings->options[i], -1);
+        if (option == NULL) {
+            return -1;
         }
-        if (c == 'E' || c == 'I') {
-            core_config.ignore_environment++;
-            break;
+        if (_PySys_AddWarnOptionWithError(option)) {
+            Py_DECREF(option);
+            return -1;
         }
+        Py_DECREF(option);
     }
+    return 0;
+}
 
-    /* Initialize the core language runtime */
-    Py_IgnoreEnvironmentFlag = core_config.ignore_environment;
-    core_config._disable_importlib = 0;
-    core_config.allocator = Py_GETENV("PYTHONMALLOC");
-    _Py_InitializeCore(&core_config);
+static int
+pymain_add_warnings_options(_PyMain *pymain)
+{
+    PySys_ResetWarnOptions();
 
-    /* Reprocess the command line with the language runtime available */
-    if (read_command_line(argc, argv, &cmdline)) {
-        return usage(2, argv[0]);
+    if (pymain_add_warnings_optlist(&pymain->env_warning_options) < 0) {
+        pymain->err = INIT_NO_MEMORY();
+        return -1;
     }
-
-    if (cmdline.print_help) {
-        return usage(0, argv[0]);
+    if (pymain_add_warnings_optlist(&pymain->cmdline.warning_options) < 0) {
+        pymain->err = INIT_NO_MEMORY();
+        return -1;
     }
+    return 0;
+}
 
-    if (cmdline.print_version) {
-        printf("Python %s\n", cmdline.print_version >= 2 ? Py_GetVersion() : PY_VERSION);
+
+/* Get warning options from PYTHONWARNINGS environment variable.
+   Return 0 on success.
+   Set pymain->err and return -1 on error. */
+static int
+pymain_warnings_envvar(_PyMain *pymain)
+{
+    if (Py_IgnoreEnvironmentFlag) {
         return 0;
     }
 
-    PySys_ResetWarnOptions();
-    apply_command_line_and_environment(&cmdline);
-
 #ifdef MS_WINDOWS
-    if (!Py_IgnoreEnvironmentFlag && (wp = _wgetenv(L"PYTHONWARNINGS")) &&
-        *wp != L'\0') {
+    wchar_t *wp;
+
+    if ((wp = _wgetenv(L"PYTHONWARNINGS")) && *wp != L'\0') {
         wchar_t *buf, *warning, *context = NULL;
 
         buf = (wchar_t *)PyMem_RawMalloc((wcslen(wp) + 1) * sizeof(wchar_t));
-        if (buf == NULL)
-            Py_FatalError(
-               "not enough memory to copy PYTHONWARNINGS");
+        if (buf == NULL) {
+            goto out_of_memory;
+        }
         wcscpy(buf, wp);
         for (warning = wcstok_s(buf, L",", &context);
              warning != NULL;
              warning = wcstok_s(NULL, L",", &context)) {
-            PySys_AddWarnOption(warning);
+
+            if (pymain_optlist_append(&pymain->env_warning_options,
+                                      warning) < 0) {
+                PyMem_RawFree(buf);
+                goto out_of_memory;
+            }
         }
         PyMem_RawFree(buf);
     }
 #else
+    char *p;
+
     if ((p = Py_GETENV("PYTHONWARNINGS")) && *p != '\0') {
         char *buf, *oldloc;
-        PyObject *unicode;
 
         /* settle for strtok here as there's no one standard
            C89 wcstok */
         buf = (char *)PyMem_RawMalloc(strlen(p) + 1);
-        if (buf == NULL)
-            Py_FatalError(
-               "not enough memory to copy PYTHONWARNINGS");
+        if (buf == NULL) {
+            goto out_of_memory;
+        }
         strcpy(buf, p);
         oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL));
         setlocale(LC_ALL, "");
         for (p = strtok(buf, ","); p != NULL; p = strtok(NULL, ",")) {
-#ifdef __APPLE__
-            /* Use utf-8 on Mac OS X */
-            unicode = PyUnicode_FromString(p);
-#else
-            unicode = PyUnicode_DecodeLocale(p, "surrogateescape");
-#endif
-            if (unicode == NULL) {
-                /* ignore errors */
-                PyErr_Clear();
-                continue;
+            size_t len;
+            wchar_t *warning = Py_DecodeLocale(p, &len);
+            if (warning == NULL) {
+                if (len == (size_t)-2) {
+                    pymain->err = _Py_INIT_ERR("failed to decode "
+                                               "PYTHONWARNINGS");
+                    return -1;
+                }
+                else {
+                    goto out_of_memory;
+                }
             }
-            PySys_AddWarnOptionUnicode(unicode);
-            Py_DECREF(unicode);
+            if (pymain_optlist_append(&pymain->env_warning_options,
+                                      warning) < 0) {
+                PyMem_RawFree(warning);
+                goto out_of_memory;
+            }
+            PyMem_RawFree(warning);
         }
         setlocale(LC_ALL, oldloc);
         PyMem_RawFree(oldloc);
         PyMem_RawFree(buf);
     }
 #endif
-    if (cmdline.warning_options != NULL) {
-        Py_ssize_t i;
-        for (i = 0; i < PyList_GET_SIZE(cmdline.warning_options); i++) {
-            PySys_AddWarnOptionUnicode(PyList_GET_ITEM(cmdline.warning_options, i));
-        }
-        Py_DECREF(cmdline.warning_options);
-    }
+    return 0;
 
-    stdin_is_interactive = Py_FdIsInteractive(stdin, (char *)0);
+out_of_memory:
+    pymain->err = INIT_NO_MEMORY();
+    return -1;
+}
+
+
+static void
+pymain_init_stdio(_PyMain *pymain)
+{
+    pymain->stdin_is_interactive = (isatty(fileno(stdin))
+                                    || Py_InteractiveFlag);
 
 #if defined(MS_WINDOWS) || defined(__CYGWIN__)
     /* don't translate newlines (\r\n <=> \n) */
@@ -706,8 +853,20 @@ Py_Main(int argc, wchar_t **argv)
 #endif /* !MS_WINDOWS */
         /* Leave stderr alone - it should be unbuffered anyway. */
     }
+}
+
+
+/* Get the program name: use PYTHONEXECUTABLE and __PYVENV_LAUNCHER__
+   environment variables on macOS if available, use argv[0] by default.
 
+   Return 0 on success.
+   Set pymain->err and return -1 on error. */
+static int
+pymain_get_program_name(_PyMain *pymain)
+{
+    assert(pymain->program_name == NULL);
 #ifdef __APPLE__
+    char *p;
     /* On MacOS X, when the Python interpreter is embedded in an
        application bundle, it gets executed by a bootstrapping script
        that does os.execve() with an argv[0] that's different from the
@@ -723,171 +882,295 @@ Py_Main(int argc, wchar_t **argv)
 
         buffer = PyMem_RawMalloc(len * sizeof(wchar_t));
         if (buffer == NULL) {
-            Py_FatalError(
-               "not enough memory to copy PYTHONEXECUTABLE");
+            goto out_of_memory;
         }
 
         mbstowcs(buffer, p, len);
-        Py_SetProgramName(buffer);
-        /* buffer is now handed off - do not free */
-    } else {
+        pymain->program_name = buffer;
+    }
 #ifdef WITH_NEXT_FRAMEWORK
+    else {
         char* pyvenv_launcher = getenv("__PYVENV_LAUNCHER__");
-
         if (pyvenv_launcher && *pyvenv_launcher) {
             /* Used by Mac/Tools/pythonw.c to forward
              * the argv0 of the stub executable
              */
-            wchar_t* wbuf = Py_DecodeLocale(pyvenv_launcher, NULL);
-
+            size_t len;
+            wchar_t* wbuf = Py_DecodeLocale(pyvenv_launcher, &len);
             if (wbuf == NULL) {
-                Py_FatalError("Cannot decode __PYVENV_LAUNCHER__");
+                if (len == (size_t)-2) {
+                    pymain->err = _Py_INIT_ERR("failed to decode "
+                                               "__PYVENV_LAUNCHER__");
+                    return -1;
+                }
+                else {
+                    goto out_of_memory;
+                }
             }
-            Py_SetProgramName(wbuf);
-
-            /* Don't free wbuf, the argument to Py_SetProgramName
-             * must remain valid until Py_FinalizeEx is called.
-             */
-        } else {
-            Py_SetProgramName(argv[0]);
+            pymain->program_name = wbuf;
         }
-#else
-        Py_SetProgramName(argv[0]);
-#endif
     }
-#else
-    Py_SetProgramName(argv[0]);
-#endif
-    /* Replaces previous call to Py_Initialize()
-     *
-     * TODO: Move environment queries (etc) into Py_ReadConfig
-     */
-    {
-        _PyMainInterpreterConfig config = _PyMainInterpreterConfig_INIT;
+#endif   /* WITH_NEXT_FRAMEWORK */
+#endif   /* __APPLE__ */
+
+    if (pymain->program_name == NULL) {
+        /* Use argv[0] by default */
+        pymain->program_name = pymain_strdup(pymain->argv[0]);
+        if (pymain->program_name == NULL) {
+            goto out_of_memory;
+        }
+    }
+    return 0;
+
+out_of_memory:
+    pymain->err = INIT_NO_MEMORY();
+    return -1;
+}
 
-        /* TODO: Moar config options! */
-        config.install_signal_handlers = 1;
-        /* TODO: Print any exceptions raised by these operations */
-        if (_Py_ReadMainInterpreterConfig(&config))
-            Py_FatalError("Py_Main: Py_ReadMainInterpreterConfig failed");
-        if (_Py_InitializeMainInterpreter(&config))
-            Py_FatalError("Py_Main: Py_InitializeMainInterpreter failed");
+
+/* Initialize the main interpreter.
+ *
+ * Replaces previous call to Py_Initialize()
+ *
+ * TODO: Move environment queries (etc) into Py_ReadConfig
+ *
+ * Return 0 on success.
+ * Set pymain->err and return -1 on error.
+ */
+static int
+pymain_init_main_interpreter(_PyMain *pymain)
+{
+    _PyMainInterpreterConfig config = _PyMainInterpreterConfig_INIT;
+    _PyInitError err;
+
+    /* TODO: Moar config options! */
+    config.install_signal_handlers = 1;
+
+    /* TODO: Print any exceptions raised by these operations */
+    err = _Py_ReadMainInterpreterConfig(&config);
+    if (_Py_INIT_FAILED(err)) {
+        pymain->err = err;
+        return -1;
     }
 
+    err = _Py_InitializeMainInterpreter(&config);
+    if (_Py_INIT_FAILED(err)) {
+        pymain->err = err;
+        return -1;
+    }
+    return 0;
+}
+
+
+static void
+pymain_header(_PyMain *pymain)
+{
     /* TODO: Move this to _PyRun_PrepareMain */
-    if (!Py_QuietFlag && (Py_VerboseFlag ||
-                        (cmdline.command == NULL && cmdline.filename == NULL &&
-                         cmdline.module == NULL && stdin_is_interactive))) {
-        fprintf(stderr, "Python %s on %s\n",
-            Py_GetVersion(), Py_GetPlatform());
-        if (!Py_NoSiteFlag)
-            fprintf(stderr, "%s\n", COPYRIGHT);
+    if (Py_QuietFlag) {
+        return;
+    }
+
+    if (!Py_VerboseFlag && (pymain->run_code || !pymain->stdin_is_interactive)) {
+        return;
     }
 
+    fprintf(stderr, "Python %s on %s\n", Py_GetVersion(), Py_GetPlatform());
+    if (!Py_NoSiteFlag) {
+        fprintf(stderr, "%s\n", COPYRIGHT);
+    }
+}
+
+
+static void
+pymain_init_argv(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+
     /* TODO: Move this to _Py_InitializeMainInterpreter */
-    if (cmdline.command != NULL) {
+    if (cmdline->command != NULL) {
         /* Backup _PyOS_optind and force sys.argv[0] = '-c' */
         _PyOS_optind--;
-        argv[_PyOS_optind] = L"-c";
+        pymain->argv[_PyOS_optind] = L"-c";
     }
 
-    if (cmdline.module != NULL) {
+    if (cmdline->module != NULL) {
         /* Backup _PyOS_optind and force sys.argv[0] = '-m'*/
         _PyOS_optind--;
-        argv[_PyOS_optind] = L"-m";
+        pymain->argv[_PyOS_optind] = L"-m";
     }
 
-    if (cmdline.filename != NULL) {
-        main_importer_path = AsImportPathEntry(cmdline.filename);
+    if (cmdline->filename != NULL) {
+        pymain->main_importer_path = pymain_get_importer(cmdline->filename);
     }
 
-    if (main_importer_path != NULL) {
-        /* Let RunMainFromImporter adjust sys.path[0] later */
-        PySys_SetArgvEx(argc-_PyOS_optind, argv+_PyOS_optind, 0);
+    int update_path;
+    if (pymain->main_importer_path != NULL) {
+        /* Let pymain_run_main_from_importer() adjust sys.path[0] later */
+        update_path = 0;
     } else {
         /* Use config settings to decide whether or not to update sys.path[0] */
-        PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind);
+        update_path = (Py_IsolatedFlag == 0);
     }
+    PySys_SetArgvEx(pymain->argc - _PyOS_optind,
+                    pymain->argv + _PyOS_optind,
+                    update_path);
+}
 
-    if ((Py_InspectFlag || (cmdline.command == NULL &&
-                            cmdline.filename == NULL &&
-                            cmdline.module == NULL)) &&
-                            isatty(fileno(stdin)) &&
-                            !Py_IsolatedFlag) {
-        PyObject *v;
-        v = PyImport_ImportModule("readline");
-        if (v == NULL)
-            PyErr_Clear();
-        else
-            Py_DECREF(v);
+
+/* Set Py_XXX global configuration variables */
+static void
+pymain_set_global_config(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+    maybe_set_flag(&Py_BytesWarningFlag, cmdline->bytes_warning);
+    maybe_set_flag(&Py_DebugFlag, cmdline->debug);
+    maybe_set_flag(&Py_InspectFlag, cmdline->inspect);
+    maybe_set_flag(&Py_InteractiveFlag, cmdline->interactive);
+    maybe_set_flag(&Py_IsolatedFlag, cmdline->isolated);
+    maybe_set_flag(&Py_OptimizeFlag, cmdline->optimization_level);
+    maybe_set_flag(&Py_DontWriteBytecodeFlag, cmdline->dont_write_bytecode);
+    maybe_set_flag(&Py_NoUserSiteDirectory, cmdline->no_user_site_directory);
+    maybe_set_flag(&Py_NoSiteFlag, cmdline->no_site_import);
+    maybe_set_flag(&Py_UnbufferedStdioFlag, cmdline->use_unbuffered_io);
+    maybe_set_flag(&Py_VerboseFlag, cmdline->verbosity);
+    maybe_set_flag(&Py_QuietFlag, cmdline->quiet_flag);
+
+    maybe_set_flag(&Py_IgnoreEnvironmentFlag, pymain->core_config.ignore_environment);
+}
+
+
+/* Propagate options parsed from the command line and environment variables
+   to the Python runtime.
+
+   Return 0 on success, or set pymain->err and return -1 on error. */
+static int
+pymain_configure_pyruntime(_PyMain *pymain)
+{
+    Py_SetProgramName(pymain->program_name);
+    /* Don't free program_name here: the argument to Py_SetProgramName
+       must remain valid until Py_FinalizeEx is called. The string is freed
+       by pymain_free(). */
+
+    if (pymain_add_xoptions(pymain)) {
+        return -1;
+    }
+    if (pymain_add_warnings_options(pymain)) {
+        return -1;
+    }
+    return 0;
+}
+
+
+static void
+pymain_import_readline(_PyMain *pymain)
+{
+    if (Py_IsolatedFlag) {
+        return;
+    }
+    if (!Py_InspectFlag && pymain->run_code) {
+        return;
+    }
+    if (!isatty(fileno(stdin))) {
+        return;
     }
 
-    if (cmdline.command) {
-        sts = run_command(cmdline.command, &cf);
-        PyMem_RawFree(cmdline.command);
-    } else if (cmdline.module) {
-        sts = (RunModule(cmdline.module, 1) != 0);
+    PyObject *mod = PyImport_ImportModule("readline");
+    if (mod == NULL) {
+        PyErr_Clear();
     }
     else {
+        Py_DECREF(mod);
+    }
+}
 
-        if (cmdline.filename == NULL && stdin_is_interactive) {
-            Py_InspectFlag = 0; /* do exit on SystemExit */
-            RunStartupFile(&cf);
-            RunInteractiveHook();
-        }
-        /* XXX */
-
-        sts = -1;               /* keep track of whether we've already run __main__ */
 
-        if (main_importer_path != NULL) {
-            sts = RunMainFromImporter(main_importer_path);
-        }
+static FILE*
+pymain_open_filename(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+    FILE* fp;
+
+    fp = _Py_wfopen(cmdline->filename, L"r");
+    if (fp == NULL) {
+        char *cfilename_buffer;
+        const char *cfilename;
+        int err = errno;
+        cfilename_buffer = Py_EncodeLocale(cmdline->filename, NULL);
+        if (cfilename_buffer != NULL)
+            cfilename = cfilename_buffer;
+        else
+            cfilename = "<unprintable file name>";
+        fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",
+                pymain->argv[0], cfilename, err, strerror(err));
+        PyMem_Free(cfilename_buffer);
+        pymain->status = 2;
+        return NULL;
+    }
 
-        if (sts==-1 && cmdline.filename != NULL) {
-            fp = _Py_wfopen(cmdline.filename, L"r");
-            if (fp == NULL) {
-                char *cfilename_buffer;
-                const char *cfilename;
-                int err = errno;
-                cfilename_buffer = Py_EncodeLocale(cmdline.filename, NULL);
-                if (cfilename_buffer != NULL)
-                    cfilename = cfilename_buffer;
-                else
-                    cfilename = "<unprintable file name>";
-                fprintf(stderr, "%ls: can't open file '%s': [Errno %d] %s\n",
-                    argv[0], cfilename, err, strerror(err));
-                if (cfilename_buffer)
-                    PyMem_Free(cfilename_buffer);
-                return 2;
-            }
-            else if (cmdline.skip_first_line) {
-                int ch;
-                /* Push back first newline so line numbers
-                   remain the same */
-                while ((ch = getc(fp)) != EOF) {
-                    if (ch == '\n') {
-                        (void)ungetc(ch, fp);
-                        break;
-                    }
-                }
-            }
-            {
-                struct _Py_stat_struct sb;
-                if (_Py_fstat_noraise(fileno(fp), &sb) == 0 &&
-                    S_ISDIR(sb.st_mode)) {
-                    fprintf(stderr,
-                            "%ls: '%ls' is a directory, cannot continue\n",
-                            argv[0], cmdline.filename);
-                    fclose(fp);
-                    return 1;
-                }
+    if (cmdline->skip_first_line) {
+        int ch;
+        /* Push back first newline so line numbers
+           remain the same */
+        while ((ch = getc(fp)) != EOF) {
+            if (ch == '\n') {
+                (void)ungetc(ch, fp);
+                break;
             }
         }
+    }
+
+    struct _Py_stat_struct sb;
+    if (_Py_fstat_noraise(fileno(fp), &sb) == 0 &&
+            S_ISDIR(sb.st_mode)) {
+        fprintf(stderr,
+                "%ls: '%ls' is a directory, cannot continue\n",
+                pymain->argv[0], cmdline->filename);
+        fclose(fp);
+        pymain->status = 1;
+        return NULL;
+    }
+
+    return fp;
+}
 
-        if (sts == -1)
-            sts = run_file(fp, cmdline.filename, &cf);
+
+static void
+pymain_run(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+
+    if (cmdline->filename == NULL && pymain->stdin_is_interactive) {
+        Py_InspectFlag = 0; /* do exit on SystemExit */
+        pymain_run_statup(&pymain->cf);
+        pymain_run_interactive_hook();
+    }
+    /* XXX */
+
+    if (pymain->main_importer_path != NULL) {
+        pymain->status = pymain_run_main_from_importer(pymain);
+        return;
+    }
+
+    FILE *fp;
+    if (cmdline->filename != NULL) {
+        fp = pymain_open_filename(pymain);
+        if (fp == NULL) {
+            return;
+        }
+    }
+    else {
+        fp = stdin;
     }
 
+    pymain->status = pymain_run_file(fp, cmdline->filename, &pymain->cf);
+}
+
+
+static void
+pymain_repl(_PyMain *pymain)
+{
+    char *p;
+
     /* Check this environment variable at the end, to give programs the
      * opportunity to set it from Python.
      */
@@ -897,34 +1180,185 @@ Py_Main(int argc, wchar_t **argv)
         Py_InspectFlag = 1;
     }
 
-    if (Py_InspectFlag && stdin_is_interactive &&
-        (cmdline.filename != NULL || cmdline.command != NULL || cmdline.module != NULL)) {
+    if (Py_InspectFlag && pymain->stdin_is_interactive && pymain->run_code) {
         Py_InspectFlag = 0;
-        RunInteractiveHook();
+        pymain_run_interactive_hook();
         /* XXX */
-        sts = PyRun_AnyFileFlags(stdin, "<stdin>", &cf) != 0;
+        int res = PyRun_AnyFileFlags(stdin, "<stdin>", &pymain->cf);
+        pymain->status = (res != 0);
+    }
+}
+
+
+/* Parse the command line.
+   Handle --version and --help options directly.
+
+   Return 1 if Python must exit.
+   Return 0 on success.
+   Set pymain->err and return -1 on failure. */
+static int
+pymain_init_cmdline(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+
+    int res = pymain_parse_cmdline(pymain);
+    if (res < 0) {
+        return -1;
+    }
+    if (res) {
+        usage(1, pymain->argv[0]);
+        pymain->status = 2;
+        return 1;
+    }
+
+    if (cmdline->print_help) {
+        usage(0, pymain->argv[0]);
+        pymain->status = 0;
+        return 1;
+    }
+
+    if (cmdline->print_version) {
+        printf("Python %s\n",
+               (cmdline->print_version >= 2) ? Py_GetVersion() : PY_VERSION);
+        return 1;
+    }
+
+    pymain->run_code = (cmdline->command != NULL || cmdline->filename != NULL
+                        || cmdline->module != NULL);
+
+    return 0;
+}
+
+
+/* Initialize Py_Main().
+   This code must not use Python runtime apart PyMem_Raw memory allocator.
+
+   Return 0 on success.
+   Return 1 if Python is done and must exit.
+   Set pymain->err and return -1 on error. */
+static int
+pymain_init_impl(_PyMain *pymain)
+{
+    _PyCoreConfig *core_config = &pymain->core_config;
+    core_config->_disable_importlib = 0;
+
+    orig_argc = pymain->argc;           /* For Py_GetArgcArgv() */
+    orig_argv = pymain->argv;
+
+    /* Parse the command line */
+    int res = pymain_init_cmdline(pymain);
+    if (res < 0) {
+        return -1;
+    }
+    if (res > 0) {
+        return 1;
+    }
+
+    pymain_set_global_config(pymain);
+    pymain_init_stdio(pymain);
+
+    /* Get environment variables */
+    if (pymain_warnings_envvar(pymain) < 0) {
+        return -1;
+    }
+    if (pymain_get_program_name(pymain) < 0) {
+        return -1;
     }
+    core_config->allocator = Py_GETENV("PYTHONMALLOC");
+
+    return 0;
+}
+
+
+static int
+pymain_init(_PyMain *pymain)
+{
+    pymain->err = _PyRuntime_Initialize();
+    if (_Py_INIT_FAILED(pymain->err)) {
+        return -1;
+    }
+
+    /* Make sure that all memory allocated in pymain_init() is allocated
+       by malloc() */
+    PyMemAllocatorEx old_alloc, raw_alloc;
+    PyMem_GetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+    _PyMem_GetDefaultRawAllocator(&raw_alloc);
+    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &raw_alloc);
+
+    int res = pymain_init_impl(pymain);
+
+    /* Restore the old memory allocator */
+    PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
+
+    return res;
+}
+
+static int
+pymain_core(_PyMain *pymain)
+{
+    _Py_CommandLineDetails *cmdline = &pymain->cmdline;
+
+    pymain->err = _Py_InitializeCore(&pymain->core_config);
+    if (_Py_INIT_FAILED(pymain->err)) {
+        return -1;
+    }
+
+    if (pymain_configure_pyruntime(pymain)) {
+        return -1;
+    }
+
+    if (pymain_init_main_interpreter(pymain)) {
+        return -1;
+    }
+
+    pymain_header(pymain);
+    pymain_import_readline(pymain);
+
+    pymain_init_argv(pymain);
+
+    if (cmdline->command) {
+        pymain->status = pymain_run_command(cmdline->command, &pymain->cf);
+    }
+    else if (cmdline->module) {
+        pymain->status = (pymain_run_module(cmdline->module, 1) != 0);
+    }
+    else {
+        pymain_run(pymain);
+    }
+    pymain_repl(pymain);
 
     if (Py_FinalizeEx() < 0) {
         /* Value unlikely to be confused with a non-error exit status or
         other special meaning */
-        sts = 120;
+        pymain->status = 120;
     }
 
-#ifdef __INSURE__
-    /* Insure++ is a memory analysis tool that aids in discovering
-     * memory leaks and other memory problems.  On Python exit, the
-     * interned string dictionaries are flagged as being in use at exit
-     * (which it is).  Under normal circumstances, this is fine because
-     * the memory will be automatically reclaimed by the system.  Under
-     * memory debugging, it's a huge source of useless noise, so we
-     * trade off slower shutdown for less distraction in the memory
-     * reports.  -baw
-     */
-    _Py_ReleaseInternedUnicodeStrings();
-#endif /* __INSURE__ */
+    return 0;
+}
+
+
+int
+Py_Main(int argc, wchar_t **argv)
+{
+    _PyMain pymain = _PyMain_INIT;
+    memset(&pymain.cmdline, 0, sizeof(pymain.cmdline));
+    pymain.argc = argc;
+    pymain.argv = argv;
+
+    int res = pymain_init(&pymain);
+    if (res < 0) {
+        _Py_FatalInitError(pymain.err);
+    }
+    if (res == 0) {
+        res = pymain_core(&pymain);
+        if (res < 0) {
+            _Py_FatalInitError(pymain.err);
+        }
+    }
+
+    pymain_free(&pymain);
 
-    return sts;
+    return pymain.status;
 }
 
 /* this is gonna seem *real weird*, but if you put some other code between
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index 4441c82ad86..699cce94d2c 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -182,31 +182,17 @@ static struct {
 
 
 #define _PyMem_Raw _PyRuntime.mem.allocators.raw
-static const PyMemAllocatorEx _pymem_raw = {
-#ifdef Py_DEBUG
-    &_PyMem_Debug.raw, PYRAWDBG_FUNCS
-#else
-    NULL, PYRAW_FUNCS
-#endif
-    };
 
 #define _PyMem _PyRuntime.mem.allocators.mem
-static const PyMemAllocatorEx _pymem = {
-#ifdef Py_DEBUG
-    &_PyMem_Debug.mem, PYDBG_FUNCS
-#else
-    NULL, PYMEM_FUNCS
-#endif
-    };
 
 #define _PyObject _PyRuntime.mem.allocators.obj
-static const PyMemAllocatorEx _pyobject = {
-#ifdef Py_DEBUG
-    &_PyMem_Debug.obj, PYDBG_FUNCS
-#else
-    NULL, PYOBJ_FUNCS
-#endif
-    };
+
+void
+_PyMem_GetDefaultRawAllocator(PyMemAllocatorEx *alloc_p)
+{
+    PyMemAllocatorEx alloc = {NULL, PYRAW_FUNCS};
+    *alloc_p = alloc;
+}
 
 int
 _PyMem_SetupAllocators(const char *opt)
@@ -267,34 +253,52 @@ _PyMem_SetupAllocators(const char *opt)
     return 0;
 }
 
-#undef PYRAW_FUNCS
-#undef PYMEM_FUNCS
-#undef PYOBJ_FUNCS
-#undef PYRAWDBG_FUNCS
-#undef PYDBG_FUNCS
 
-static const PyObjectArenaAllocator _PyObject_Arena = {NULL,
+void
+_PyObject_Initialize(struct _pyobj_runtime_state *state)
+{
+    PyObjectArenaAllocator _PyObject_Arena = {NULL,
 #ifdef MS_WINDOWS
-    _PyObject_ArenaVirtualAlloc, _PyObject_ArenaVirtualFree
+        _PyObject_ArenaVirtualAlloc, _PyObject_ArenaVirtualFree
 #elif defined(ARENAS_USE_MMAP)
-    _PyObject_ArenaMmap, _PyObject_ArenaMunmap
+        _PyObject_ArenaMmap, _PyObject_ArenaMunmap
 #else
-    _PyObject_ArenaMalloc, _PyObject_ArenaFree
+        _PyObject_ArenaMalloc, _PyObject_ArenaFree
 #endif
     };
 
-void
-_PyObject_Initialize(struct _pyobj_runtime_state *state)
-{
     state->allocator_arenas = _PyObject_Arena;
 }
 
+
 void
 _PyMem_Initialize(struct _pymem_runtime_state *state)
 {
-    state->allocators.raw = _pymem_raw;
-    state->allocators.mem = _pymem;
-    state->allocators.obj = _pyobject;
+    PyMemAllocatorEx pymem_raw = {
+#ifdef Py_DEBUG
+        &_PyMem_Debug.raw, PYRAWDBG_FUNCS
+#else
+        NULL, PYRAW_FUNCS
+#endif
+    };
+    PyMemAllocatorEx pymem = {
+#ifdef Py_DEBUG
+        &_PyMem_Debug.mem, PYDBG_FUNCS
+#else
+        NULL, PYMEM_FUNCS
+#endif
+    };
+    PyMemAllocatorEx pyobject = {
+#ifdef Py_DEBUG
+        &_PyMem_Debug.obj, PYDBG_FUNCS
+#else
+        NULL, PYOBJ_FUNCS
+#endif
+    };
+
+    state->allocators.raw = pymem_raw;
+    state->allocators.mem = pymem;
+    state->allocators.obj = pyobject;
 
 #ifdef WITH_PYMALLOC
     Py_BUILD_ASSERT(NB_SMALL_SIZE_CLASSES == 64);
@@ -311,6 +315,7 @@ _PyMem_Initialize(struct _pymem_runtime_state *state)
 #endif /* WITH_PYMALLOC */
 }
 
+
 #ifdef WITH_PYMALLOC
 static int
 _PyMem_DebugEnabled(void)
diff --git a/PCbuild/python.vcxproj b/PCbuild/python.vcxproj
index ab9fb05adea..423e68c57b2 100644
--- a/PCbuild/python.vcxproj
+++ b/PCbuild/python.vcxproj
@@ -57,7 +57,7 @@
   </PropertyGroup>
   <ItemDefinitionGroup>
     <ClCompile>
-      <PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <PreprocessorDefinitions>Py_BUILD_CORE;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
     </ClCompile>
     <Link>
       <SubSystem>Console</SubSystem>
@@ -113,4 +113,4 @@ $(_PGOPath)
     </PropertyGroup>
     <WriteLinesToFile File="$(PySourcePath)python.bat" Lines="$(_Content)" Overwrite="true" Condition="'$(_Content)' != '$(_ExistingContent)'" />
   </Target>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/Parser/pgenmain.c b/Parser/pgenmain.c
index 8f3e2f7c4ee..dbdf13440af 100644
--- a/Parser/pgenmain.c
+++ b/Parser/pgenmain.c
@@ -27,13 +27,11 @@
 int Py_DebugFlag;
 int Py_VerboseFlag;
 int Py_IgnoreEnvironmentFlag;
-_PyRuntimeState _PyRuntime = {0, 0};
+_PyRuntimeState _PyRuntime = _PyRuntimeState_INIT;
 
 /* Forward */
 grammar *getgrammar(const char *filename);
 
-void Py_Exit(int) _Py_NO_RETURN;
-
 void
 Py_Exit(int sts)
 {
diff --git a/Programs/_freeze_importlib.c b/Programs/_freeze_importlib.c
index 1069966a187..b8b630cfed3 100644
--- a/Programs/_freeze_importlib.c
+++ b/Programs/_freeze_importlib.c
@@ -81,7 +81,10 @@ main(int argc, char *argv[])
 
     Py_SetProgramName(L"./_freeze_importlib");
     /* Don't install importlib, since it could execute outdated bytecode. */
-    _Py_InitializeEx_Private(1, 0);
+    _PyInitError err = _Py_InitializeEx_Private(1, 0);
+    if (_Py_INIT_FAILED(err)) {
+        _Py_FatalInitError(err);
+    }
 
     if (strstr(inpath, "_external") != NULL) {
         is_bootstrap = 0;
diff --git a/Programs/python.c b/Programs/python.c
index 270a11babb8..707e38f257d 100644
--- a/Programs/python.c
+++ b/Programs/python.c
@@ -1,6 +1,7 @@
 /* Minimal main program -- everything is loaded from the library */
 
 #include "Python.h"
+#include "internal/pystate.h"
 #include <locale.h>
 
 #ifdef __FreeBSD__
@@ -22,9 +23,16 @@ main(int argc, char **argv)
     wchar_t **argv_copy;
     /* We need a second copy, as Python might modify the first one. */
     wchar_t **argv_copy2;
-    int i, res;
+    int i, status;
     char *oldloc;
 
+    _PyInitError err = _PyRuntime_Initialize();
+    if (_Py_INIT_FAILED(err)) {
+        fprintf(stderr, "Fatal Python error: %s\n", err.msg);
+        fflush(stderr);
+        exit(1);
+    }
+
     /* Force malloc() allocator to bootstrap Python */
 #ifdef Py_DEBUG
     (void)_PyMem_SetupAllocators("malloc_debug");
@@ -88,7 +96,7 @@ main(int argc, char **argv)
     setlocale(LC_ALL, oldloc);
     PyMem_RawFree(oldloc);
 
-    res = Py_Main(argc, argv_copy);
+    status = Py_Main(argc, argv_copy);
 
     /* Force again malloc() allocator to release memory blocks allocated
        before Py_Main() */
@@ -103,6 +111,6 @@ main(int argc, char **argv)
     }
     PyMem_RawFree(argv_copy);
     PyMem_RawFree(argv_copy2);
-    return res;
+    return status;
 }
 #endif
diff --git a/Python/bootstrap_hash.c b/Python/bootstrap_hash.c
index 807d6023ce4..e667fa93f0a 100644
--- a/Python/bootstrap_hash.c
+++ b/Python/bootstrap_hash.c
@@ -561,15 +561,16 @@ int Py_ReadHashSeed(char *seed_text,
     return 0;
 }
 
-static void
+static _PyInitError
 init_hash_secret(int use_hash_seed,
                  unsigned long hash_seed)
 {
     void *secret = &_Py_HashSecret;
     Py_ssize_t secret_size = sizeof(_Py_HashSecret_t);
 
-    if (_Py_HashSecret_Initialized)
-        return;
+    if (_Py_HashSecret_Initialized) {
+        return _Py_INIT_OK();
+    }
     _Py_HashSecret_Initialized = 1;
 
     if (use_hash_seed) {
@@ -593,12 +594,14 @@ init_hash_secret(int use_hash_seed,
            pyurandom() is non-blocking mode (blocking=0): see the PEP 524. */
         res = pyurandom(secret, secret_size, 0, 0);
         if (res < 0) {
-            Py_FatalError("failed to get random numbers to initialize Python");
+            return _Py_INIT_ERR("failed to get random numbers "
+                                "to initialize Python");
         }
     }
+    return _Py_INIT_OK();
 }
 
-void
+_PyInitError
 _Py_HashRandomization_Init(_PyCoreConfig *core_config)
 {
     char *seed_text;
@@ -608,13 +611,13 @@ _Py_HashRandomization_Init(_PyCoreConfig *core_config)
     if (use_hash_seed < 0) {
         seed_text = Py_GETENV("PYTHONHASHSEED");
         if (Py_ReadHashSeed(seed_text, &use_hash_seed, &hash_seed) < 0) {
-            Py_FatalError("PYTHONHASHSEED must be \"random\" or an integer "
-                          "in range [0; 4294967295]");
+            return _Py_INIT_USER_ERR("PYTHONHASHSEED must be \"random\" "
+                                     "or an integer in range [0; 4294967295]");
         }
         core_config->use_hash_seed = use_hash_seed;
         core_config->hash_seed = hash_seed;
     }
-    init_hash_secret(use_hash_seed, hash_seed);
+    return init_hash_secret(use_hash_seed, hash_seed);
 }
 
 void
diff --git a/Python/frozenmain.c b/Python/frozenmain.c
index 769b33d0ee2..77602d70fa8 100644
--- a/Python/frozenmain.c
+++ b/Python/frozenmain.c
@@ -2,6 +2,7 @@
 /* Python interpreter main program for frozen scripts */
 
 #include "Python.h"
+#include "internal/pystate.h"
 #include <locale.h>
 
 #ifdef MS_WINDOWS
@@ -15,6 +16,13 @@ extern int PyInitFrozenExtensions(void);
 int
 Py_FrozenMain(int argc, char **argv)
 {
+    _PyInitError err = _PyRuntime_Initialize();
+    if (_Py_INIT_FAILED(err)) {
+        fprintf(stderr, "Fatal Python error: %s\n", err.msg);
+        fflush(stderr);
+        exit(1);
+    }
+
     char *p;
     int i, n, sts = 1;
     int inspect = 0;
diff --git a/Python/import.c b/Python/import.c
index 950c872ccb8..fe60844ef18 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -42,19 +42,23 @@ module _imp
 
 /* Initialize things */
 
-void
+_PyInitError
 _PyImport_Init(void)
 {
     PyInterpreterState *interp = PyThreadState_Get()->interp;
     initstr = PyUnicode_InternFromString("__init__");
-    if (initstr == NULL)
-        Py_FatalError("Can't initialize import variables");
+    if (initstr == NULL) {
+        return _Py_INIT_ERR("Can't initialize import variables");
+    }
+
     interp->builtins_copy = PyDict_Copy(interp->builtins);
-    if (interp->builtins_copy == NULL)
-        Py_FatalError("Can't backup builtins dict");
+    if (interp->builtins_copy == NULL) {
+        return _Py_INIT_ERR("Can't backup builtins dict");
+    }
+    return _Py_INIT_OK();
 }
 
-void
+_PyInitError
 _PyImportHooks_Init(void)
 {
     PyObject *v, *path_hooks = NULL;
@@ -80,15 +84,18 @@ _PyImportHooks_Init(void)
         goto error;
     err = PySys_SetObject("path_hooks", path_hooks);
     if (err) {
-  error:
-    PyErr_Print();
-    Py_FatalError("initializing sys.meta_path, sys.path_hooks, "
-                  "or path_importer_cache failed");
+        goto error;
     }
     Py_DECREF(path_hooks);
+    return _Py_INIT_OK();
+
+  error:
+    PyErr_Print();
+    return _Py_INIT_ERR("initializing sys.meta_path, sys.path_hooks, "
+                        "or path_importer_cache failed");
 }
 
-void
+_PyInitError
 _PyImportZip_Init(void)
 {
     PyObject *path_hooks, *zimpimport;
@@ -133,11 +140,11 @@ _PyImportZip_Init(void)
         }
     }
 
-    return;
+    return _Py_INIT_OK();
 
   error:
     PyErr_Print();
-    Py_FatalError("initializing zipimport failed");
+    return _Py_INIT_ERR("initializing zipimport failed");
 }
 
 /* Locking primitives to prevent parallel imports of the same module
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 8817be1c411..66cc711137c 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -53,11 +53,11 @@ extern wchar_t *Py_GetPath(void);
 extern grammar _PyParser_Grammar; /* From graminit.c */
 
 /* Forward */
-static void initmain(PyInterpreterState *interp);
-static int initfsencoding(PyInterpreterState *interp);
-static void initsite(void);
+static _PyInitError add_main_module(PyInterpreterState *interp);
+static _PyInitError initfsencoding(PyInterpreterState *interp);
+static _PyInitError initsite(void);
 static int initstdio(void);
-static void initsigs(void);
+static _PyInitError initsigs(void);
 static void call_py_exitfuncs(void);
 static void wait_for_thread_shutdown(void);
 static void call_ll_exitfuncs(void);
@@ -66,7 +66,7 @@ extern int _PyStructSequence_Init(void);
 extern void _PyUnicode_Fini(void);
 extern int _PyLong_Init(void);
 extern void PyLong_Fini(void);
-extern int _PyFaulthandler_Init(void);
+extern _PyInitError _PyFaulthandler_Init(void);
 extern void _PyFaulthandler_Fini(void);
 extern void _PyHash_Fini(void);
 extern int _PyTraceMalloc_Init(void);
@@ -76,9 +76,9 @@ extern void _Py_ReadyTypes(void);
 extern void _PyGILState_Init(PyInterpreterState *, PyThreadState *);
 extern void _PyGILState_Fini(void);
 
-_PyRuntimeState _PyRuntime = {0, 0};
+_PyRuntimeState _PyRuntime = _PyRuntimeState_INIT;
 
-void
+_PyInitError
 _PyRuntime_Initialize(void)
 {
     /* XXX We only initialize once in the process, which aligns with
@@ -88,10 +88,12 @@ _PyRuntime_Initialize(void)
        This is because the runtime state is not properly finalized
        currently. */
     static int initialized = 0;
-    if (initialized)
-        return;
+    if (initialized) {
+        return _Py_INIT_OK();
+    }
     initialized = 1;
-    _PyRuntimeState_Init(&_PyRuntime);
+
+    return _PyRuntimeState_Init(&_PyRuntime);
 }
 
 void
@@ -282,43 +284,44 @@ get_locale_encoding(void)
 #endif
 }
 
-static void
+/* Return NULL on success, or return an error message on failure */
+static _PyInitError
 initimport(PyInterpreterState *interp, PyObject *sysmod)
 {
     PyObject *importlib;
     PyObject *impmod;
     PyObject *value;
+    _PyInitError err;
 
     /* Import _importlib through its frozen version, _frozen_importlib. */
     if (PyImport_ImportFrozenModule("_frozen_importlib") <= 0) {
-        Py_FatalError("Py_Initialize: can't import _frozen_importlib");
+        return _Py_INIT_ERR("can't import _frozen_importlib");
     }
     else if (Py_VerboseFlag) {
         PySys_FormatStderr("import _frozen_importlib # frozen\n");
     }
     importlib = PyImport_AddModule("_frozen_importlib");
     if (importlib == NULL) {
-        Py_FatalError("Py_Initialize: couldn't get _frozen_importlib from "
-                      "sys.modules");
+        return _Py_INIT_ERR("couldn't get _frozen_importlib from sys.modules");
     }
     interp->importlib = importlib;
     Py_INCREF(interp->importlib);
 
     interp->import_func = PyDict_GetItemString(interp->builtins, "__import__");
     if (interp->import_func == NULL)
-        Py_FatalError("Py_Initialize: __import__ not found");
+        return _Py_INIT_ERR("__import__ not found");
     Py_INCREF(interp->import_func);
 
     /* Import the _imp module */
     impmod = PyInit_imp();
     if (impmod == NULL) {
-        Py_FatalError("Py_Initialize: can't import _imp");
+        return _Py_INIT_ERR("can't import _imp");
     }
     else if (Py_VerboseFlag) {
         PySys_FormatStderr("import _imp # builtin\n");
     }
     if (_PyImport_SetModuleString("_imp", impmod) < 0) {
-        Py_FatalError("Py_Initialize: can't save _imp to sys.modules");
+        return _Py_INIT_ERR("can't save _imp to sys.modules");
     }
 
     /* Install importlib as the implementation of import */
@@ -330,15 +333,21 @@ initimport(PyInterpreterState *interp, PyObject *sysmod)
     }
     if (value == NULL) {
         PyErr_Print();
-        Py_FatalError("Py_Initialize: importlib install failed");
+        return _Py_INIT_ERR("importlib install failed");
     }
     Py_DECREF(value);
     Py_DECREF(impmod);
 
-    _PyImportZip_Init();
+    err = _PyImportZip_Init();
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    return _Py_INIT_OK();
 }
 
-static void
+/* Return NULL on success, or return an error message on failure */
+static _PyInitError
 initexternalimport(PyInterpreterState *interp)
 {
     PyObject *value;
@@ -346,9 +355,10 @@ initexternalimport(PyInterpreterState *interp)
                                 "_install_external_importers", "");
     if (value == NULL) {
         PyErr_Print();
-        Py_FatalError("Py_EndInitialization: external importer setup failed");
+        return _Py_INIT_ERR("external importer setup failed");
     }
     Py_DECREF(value);
+    return _Py_INIT_OK();
 }
 
 /* Helper functions to better handle the legacy C locale
@@ -613,13 +623,16 @@ _Py_SetLocaleFromEnv(int category)
  * Any code invoked from this function should *not* assume it has access
  * to the Python C API (unless the API is explicitly listed as being
  * safe to call without calling Py_Initialize first)
+ *
+ * Return NULL on success, or return an error message on failure.
  */
 
 /* TODO: Progressively move functionality from Py_BeginInitialization to
  * Py_ReadConfig and Py_EndInitialization
  */
 
-void _Py_InitializeCore(const _PyCoreConfig *config)
+_PyInitError
+_Py_InitializeCore(const _PyCoreConfig *config)
 {
     PyInterpreterState *interp;
     PyThreadState *tstate;
@@ -627,18 +640,26 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
     char *p;
     _PyCoreConfig core_config = _PyCoreConfig_INIT;
     _PyMainInterpreterConfig preinit_config = _PyMainInterpreterConfig_INIT;
+    _PyInitError err;
 
-    _PyRuntime_Initialize();
+    err = _PyRuntime_Initialize();
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
     if (config != NULL) {
         core_config = *config;
     }
 
+    if (_PyMem_SetupAllocators(core_config.allocator) < 0) {
+        return _Py_INIT_ERR("Unknown PYTHONMALLOC allocator");
+    }
+
     if (_PyRuntime.initialized) {
-        Py_FatalError("Py_InitializeCore: main interpreter already initialized");
+        return _Py_INIT_ERR("main interpreter already initialized");
     }
     if (_PyRuntime.core_initialized) {
-        Py_FatalError("Py_InitializeCore: runtime core already initialized");
+        return _Py_INIT_ERR("runtime core already initialized");
     }
 
     /* Py_Finalize leaves _Py_Finalizing set in order to help daemon
@@ -652,13 +673,6 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
      */
     _PyRuntime.finalizing = NULL;
 
-    if (_PyMem_SetupAllocators(core_config.allocator) < 0) {
-        fprintf(stderr,
-            "Error in PYTHONMALLOC: unknown allocator \"%s\"!\n",
-            core_config.allocator);
-        exit(1);
-    }
-
 #ifndef MS_WINDOWS
     /* Set up the LC_CTYPE locale, so we can obtain
        the locale's charset without having to switch
@@ -692,7 +706,11 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
         set_flag(&Py_LegacyWindowsStdioFlag, p);
 #endif
 
-    _Py_HashRandomization_Init(&core_config);
+    err = _Py_HashRandomization_Init(&core_config);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
     if (!core_config.use_hash_seed || core_config.hash_seed) {
         /* Random or non-zero hash seed */
         Py_HashRandomizationFlag = 1;
@@ -701,13 +719,13 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
     _PyInterpreterState_Enable(&_PyRuntime);
     interp = PyInterpreterState_New();
     if (interp == NULL)
-        Py_FatalError("Py_InitializeCore: can't make main interpreter");
+        return _Py_INIT_ERR("can't make main interpreter");
     interp->core_config = core_config;
     interp->config = preinit_config;
 
     tstate = PyThreadState_New(interp);
     if (tstate == NULL)
-        Py_FatalError("Py_InitializeCore: can't make first thread");
+        return _Py_INIT_ERR("can't make first thread");
     (void) PyThreadState_Swap(tstate);
 
     /* We can't call _PyEval_FiniThreads() in Py_FinalizeEx because
@@ -722,46 +740,50 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
     _Py_ReadyTypes();
 
     if (!_PyFrame_Init())
-        Py_FatalError("Py_InitializeCore: can't init frames");
+        return _Py_INIT_ERR("can't init frames");
 
     if (!_PyLong_Init())
-        Py_FatalError("Py_InitializeCore: can't init longs");
+        return _Py_INIT_ERR("can't init longs");
 
     if (!PyByteArray_Init())
-        Py_FatalError("Py_InitializeCore: can't init bytearray");
+        return _Py_INIT_ERR("can't init bytearray");
 
     if (!_PyFloat_Init())
-        Py_FatalError("Py_InitializeCore: can't init float");
+        return _Py_INIT_ERR("can't init float");
 
     PyObject *modules = PyDict_New();
     if (modules == NULL)
-        Py_FatalError("Py_InitializeCore: can't make modules dictionary");
+        return _Py_INIT_ERR("can't make modules dictionary");
     interp->modules = modules;
 
-    sysmod = _PySys_BeginInit();
-    if (sysmod == NULL)
-        Py_FatalError("Py_InitializeCore: can't initialize sys");
+    err = _PySys_BeginInit(&sysmod);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
     interp->sysdict = PyModule_GetDict(sysmod);
-    if (interp->sysdict == NULL)
-        Py_FatalError("Py_InitializeCore: can't initialize sys dict");
+    if (interp->sysdict == NULL) {
+        return _Py_INIT_ERR("can't initialize sys dict");
+    }
+
     Py_INCREF(interp->sysdict);
     PyDict_SetItemString(interp->sysdict, "modules", modules);
     _PyImport_FixupBuiltin(sysmod, "sys", modules);
 
     /* Init Unicode implementation; relies on the codec registry */
     if (_PyUnicode_Init() < 0)
-        Py_FatalError("Py_InitializeCore: can't initialize unicode");
+        return _Py_INIT_ERR("can't initialize unicode");
 
     if (_PyStructSequence_Init() < 0)
-        Py_FatalError("Py_InitializeCore: can't initialize structseq");
+        return _Py_INIT_ERR("can't initialize structseq");
 
     bimod = _PyBuiltin_Init();
     if (bimod == NULL)
-        Py_FatalError("Py_InitializeCore: can't initialize builtins modules");
+        return _Py_INIT_ERR("can't initialize builtins modules");
     _PyImport_FixupBuiltin(bimod, "builtins", modules);
     interp->builtins = PyModule_GetDict(bimod);
     if (interp->builtins == NULL)
-        Py_FatalError("Py_InitializeCore: can't initialize builtins dict");
+        return _Py_INIT_ERR("can't initialize builtins dict");
     Py_INCREF(interp->builtins);
 
     /* initialize builtin exceptions */
@@ -771,25 +793,35 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
        infrastructure for the io module in place. */
     pstderr = PyFile_NewStdPrinter(fileno(stderr));
     if (pstderr == NULL)
-        Py_FatalError("Py_InitializeCore: can't set preliminary stderr");
+        return _Py_INIT_ERR("can't set preliminary stderr");
     _PySys_SetObjectId(&PyId_stderr, pstderr);
     PySys_SetObject("__stderr__", pstderr);
     Py_DECREF(pstderr);
 
-    _PyImport_Init();
+    err = _PyImport_Init();
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
-    _PyImportHooks_Init();
+    err = _PyImportHooks_Init();
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
     /* Initialize _warnings. */
     _PyWarnings_Init();
 
     /* This call sets up builtin and frozen import support */
     if (!interp->core_config._disable_importlib) {
-        initimport(interp, sysmod);
+        err = initimport(interp, sysmod);
+        if (_Py_INIT_FAILED(err)) {
+            return err;
+        }
     }
 
     /* Only when we get here is the runtime core fully initialized */
     _PyRuntime.core_initialized = 1;
+    return _Py_INIT_OK();
 }
 
 /* Read configuration settings from standard locations
@@ -802,16 +834,18 @@ void _Py_InitializeCore(const _PyCoreConfig *config)
  *
  * More advanced selective initialization tricks are possible by calling
  * this function multiple times with various preconfigured settings.
+ *
+ * Return NULL on success, or return an error message on failure.
  */
 
-int _Py_ReadMainInterpreterConfig(_PyMainInterpreterConfig *config)
+_PyInitError
+_Py_ReadMainInterpreterConfig(_PyMainInterpreterConfig *config)
 {
     /* Signal handlers are installed by default */
     if (config->install_signal_handlers < 0) {
         config->install_signal_handlers = 1;
     }
-
-    return 0;
+    return _Py_INIT_OK();
 }
 
 /* Update interpreter state based on supplied configuration settings
@@ -824,26 +858,30 @@ int _Py_ReadMainInterpreterConfig(_PyMainInterpreterConfig *config)
  * initialized or without a valid current thread state is a fatal error.
  * Other errors should be reported as normal Python exceptions with a
  * non-zero return code.
+ *
+ * Return NULL on success, or return an error message on failure.
  */
-int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
+_PyInitError
+_Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
 {
     PyInterpreterState *interp;
     PyThreadState *tstate;
+    _PyInitError err;
 
     if (!_PyRuntime.core_initialized) {
-        Py_FatalError("Py_InitializeMainInterpreter: runtime core not initialized");
+        return _Py_INIT_ERR("runtime core not initialized");
     }
     if (_PyRuntime.initialized) {
-        Py_FatalError("Py_InitializeMainInterpreter: main interpreter already initialized");
+        return _Py_INIT_ERR("main interpreter already initialized");
     }
 
     /* Get current thread state and interpreter pointer */
     tstate = PyThreadState_GET();
     if (!tstate)
-        Py_FatalError("Py_InitializeMainInterpreter: failed to read thread state");
+        return _Py_INIT_ERR("failed to read thread state");
     interp = tstate->interp;
     if (!interp)
-        Py_FatalError("Py_InitializeMainInterpreter: failed to get interpreter");
+        return _Py_INIT_ERR("failed to get interpreter");
 
     /* Now finish configuring the main interpreter */
     interp->config = *config;
@@ -855,12 +893,12 @@ int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
          * or pure Python code in the standard library won't work.
          */
         _PyRuntime.initialized = 1;
-        return 0;
+        return _Py_INIT_OK();
     }
     /* TODO: Report exceptions rather than fatal errors below here */
 
     if (_PyTime_Init() < 0)
-        Py_FatalError("Py_InitializeMainInterpreter: can't initialize time");
+        return _Py_INIT_ERR("can't initialize time");
 
     /* Finish setting up the sys module and import system */
     /* GetPath may initialize state that _PySys_EndInit locks
@@ -868,26 +906,40 @@ int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
     /* TODO: Call Py_GetPath() in Py_ReadConfig, rather than here */
     PySys_SetPath(Py_GetPath());
     if (_PySys_EndInit(interp->sysdict) < 0)
-        Py_FatalError("Py_InitializeMainInterpreter: can't finish initializing sys");
-    initexternalimport(interp);
+        return _Py_INIT_ERR("can't finish initializing sys");
+    err = initexternalimport(interp);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
     /* initialize the faulthandler module */
-    if (_PyFaulthandler_Init())
-        Py_FatalError("Py_InitializeMainInterpreter: can't initialize faulthandler");
+    err = _PyFaulthandler_Init();
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
-    if (initfsencoding(interp) < 0)
-        Py_FatalError("Py_InitializeMainInterpreter: unable to load the file system codec");
+    err = initfsencoding(interp);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
 
-    if (config->install_signal_handlers)
-        initsigs(); /* Signal handling stuff, including initintr() */
+    if (config->install_signal_handlers) {
+        err = initsigs(); /* Signal handling stuff, including initintr() */
+        if (_Py_INIT_FAILED(err)) {
+            return err;
+        }
+    }
 
     if (_PyTraceMalloc_Init() < 0)
-        Py_FatalError("Py_InitializeMainInterpreter: can't initialize tracemalloc");
+        return _Py_INIT_ERR("can't initialize tracemalloc");
 
-    initmain(interp); /* Module __main__ */
-    if (initstdio() < 0)
-        Py_FatalError(
-            "Py_InitializeMainInterpreter: can't initialize sys standard streams");
+    err = add_main_module(interp);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+    if (initstdio() < 0) {
+        return _Py_INIT_ERR("can't initialize sys standard streams");
+    }
 
     /* Initialize warnings. */
     if (PySys_HasWarnOptions()) {
@@ -901,37 +953,57 @@ int _Py_InitializeMainInterpreter(const _PyMainInterpreterConfig *config)
 
     _PyRuntime.initialized = 1;
 
-    if (!Py_NoSiteFlag)
-        initsite(); /* Module site */
+    if (!Py_NoSiteFlag) {
+        err = initsite(); /* Module site */
+        if (_Py_INIT_FAILED(err)) {
+            return err;
+        }
+    }
 
-    return 0;
+    return _Py_INIT_OK();
 }
 
 #undef _INIT_DEBUG_PRINT
 
-void
+_PyInitError
 _Py_InitializeEx_Private(int install_sigs, int install_importlib)
 {
     _PyCoreConfig core_config = _PyCoreConfig_INIT;
     _PyMainInterpreterConfig config = _PyMainInterpreterConfig_INIT;
+    _PyInitError err;
 
     /* TODO: Moar config options! */
     core_config.ignore_environment = Py_IgnoreEnvironmentFlag;
     core_config._disable_importlib = !install_importlib;
     config.install_signal_handlers = install_sigs;
-    _Py_InitializeCore(&core_config);
+
+    err = _Py_InitializeCore(&core_config);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
     /* TODO: Print any exceptions raised by these operations */
-    if (_Py_ReadMainInterpreterConfig(&config))
-        Py_FatalError("Py_Initialize: Py_ReadMainInterpreterConfig failed");
-    if (_Py_InitializeMainInterpreter(&config))
-        Py_FatalError("Py_Initialize: Py_InitializeMainInterpreter failed");
+    err = _Py_ReadMainInterpreterConfig(&config);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    err = _Py_InitializeMainInterpreter(&config);
+    if (_Py_INIT_FAILED(err)) {
+        return err;
+    }
+
+    return _Py_INIT_OK();
 }
 
 
 void
 Py_InitializeEx(int install_sigs)
 {
-    _Py_InitializeEx_Private(install_sigs, 1);
+    _PyInitError err = _Py_InitializeEx_Private(install_sigs, 1);
+    if (_Py_INIT_FAILED(err)) {
+        _Py_FatalInitError(err);
+    }
 }
 
 void
@@ -1236,6 +1308,7 @@ Py_NewInterpreter(void)
     PyInterpreterState *interp;
     PyThreadState *tstate, *save_tstate;
     PyObject *bimod, *sysmod;
+    _PyInitError err = _Py_INIT_OK();
 
     if (!_PyRuntime.initialized)
         Py_FatalError("Py_NewInterpreter: call Py_Initialize first");
@@ -1308,25 +1381,50 @@ Py_NewInterpreter(void)
         PySys_SetObject("__stderr__", pstderr);
         Py_DECREF(pstderr);
 
-        _PyImportHooks_Init();
+        err = _PyImportHooks_Init();
+        if (_Py_INIT_FAILED(err)) {
+            goto init_failed;
+        }
 
-        initimport(interp, sysmod);
-        initexternalimport(interp);
+        err = initimport(interp, sysmod);
+        if (_Py_INIT_FAILED(err)) {
+            goto init_failed;
+        }
 
-        if (initfsencoding(interp) < 0)
-            goto handle_error;
+        err = initexternalimport(interp);
+        if (_Py_INIT_FAILED(err)) {
+            goto init_failed;
+        }
 
-        if (initstdio() < 0)
-            Py_FatalError(
-                "Py_NewInterpreter: can't initialize sys standard streams");
-        initmain(interp);
-        if (!Py_NoSiteFlag)
-            initsite();
+        err = initfsencoding(interp);
+        if (_Py_INIT_FAILED(err)) {
+            goto init_failed;
+        }
+
+        if (initstdio() < 0) {
+            err = _Py_INIT_ERR("can't initialize sys standard streams");
+            goto init_failed;
+        }
+
+        err = add_main_module(interp);
+        if (_Py_INIT_FAILED(err)) {
+            goto init_failed;
+        }
+
+        if (!Py_NoSiteFlag) {
+            err = initsite();
+            if (_Py_INIT_FAILED(err)) {
+                goto init_failed;
+            }
+        }
     }
 
     if (!PyErr_Occurred())
         return tstate;
 
+init_failed:
+    _Py_FatalInitError(err);
+
 handle_error:
     /* Oops, it didn't work.  Undo it all. */
 
@@ -1417,32 +1515,35 @@ Py_GetPythonHome(void)
     return home;
 }
 
-/* Create __main__ module */
+/* Add the __main__ module */
 
-static void
-initmain(PyInterpreterState *interp)
+static _PyInitError
+add_main_module(PyInterpreterState *interp)
 {
     PyObject *m, *d, *loader, *ann_dict;
     m = PyImport_AddModule("__main__");
     if (m == NULL)
-        Py_FatalError("can't create __main__ module");
+        return _Py_INIT_ERR("can't create __main__ module");
+
     d = PyModule_GetDict(m);
     ann_dict = PyDict_New();
     if ((ann_dict == NULL) ||
         (PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {
-        Py_FatalError("Failed to initialize __main__.__annotations__");
+        return _Py_INIT_ERR("Failed to initialize __main__.__annotations__");
     }
     Py_DECREF(ann_dict);
+
     if (PyDict_GetItemString(d, "__builtins__") == NULL) {
         PyObject *bimod = PyImport_ImportModule("builtins");
         if (bimod == NULL) {
-            Py_FatalError("Failed to retrieve builtins module");
+            return _Py_INIT_ERR("Failed to retrieve builtins module");
         }
         if (PyDict_SetItemString(d, "__builtins__", bimod) < 0) {
-            Py_FatalError("Failed to initialize __main__.__builtins__");
+            return _Py_INIT_ERR("Failed to initialize __main__.__builtins__");
         }
         Py_DECREF(bimod);
     }
+
     /* Main is a little special - imp.is_builtin("__main__") will return
      * False, but BuiltinImporter is still the most appropriate initial
      * setting for its __loader__ attribute. A more suitable value will
@@ -1454,41 +1555,40 @@ initmain(PyInterpreterState *interp)
         PyObject *loader = PyObject_GetAttrString(interp->importlib,
                                                   "BuiltinImporter");
         if (loader == NULL) {
-            Py_FatalError("Failed to retrieve BuiltinImporter");
+            return _Py_INIT_ERR("Failed to retrieve BuiltinImporter");
         }
         if (PyDict_SetItemString(d, "__loader__", loader) < 0) {
-            Py_FatalError("Failed to initialize __main__.__loader__");
+            return _Py_INIT_ERR("Failed to initialize __main__.__loader__");
         }
         Py_DECREF(loader);
     }
+    return _Py_INIT_OK();
 }
 
-static int
+static _PyInitError
 initfsencoding(PyInterpreterState *interp)
 {
     PyObject *codec;
 
 #ifdef MS_WINDOWS
-    if (Py_LegacyWindowsFSEncodingFlag)
-    {
+    if (Py_LegacyWindowsFSEncodingFlag) {
         Py_FileSystemDefaultEncoding = "mbcs";
         Py_FileSystemDefaultEncodeErrors = "replace";
     }
-    else
-    {
+    else {
         Py_FileSystemDefaultEncoding = "utf-8";
         Py_FileSystemDefaultEncodeErrors = "surrogatepass";
     }
 #else
-    if (Py_FileSystemDefaultEncoding == NULL)
-    {
+    if (Py_FileSystemDefaultEncoding == NULL) {
         Py_FileSystemDefaultEncoding = get_locale_encoding();
-        if (Py_FileSystemDefaultEncoding == NULL)
-            Py_FatalError("Py_Initialize: Unable to get the locale encoding");
+        if (Py_FileSystemDefaultEncoding == NULL) {
+            return _Py_INIT_ERR("Unable to get the locale encoding");
+        }
 
         Py_HasFileSystemDefaultEncoding = 0;
         interp->fscodec_initialized = 1;
-        return 0;
+        return _Py_INIT_OK();
     }
 #endif
 
@@ -1498,29 +1598,25 @@ initfsencoding(PyInterpreterState *interp)
         /* Such error can only occurs in critical situations: no more
          * memory, import a module of the standard library failed,
          * etc. */
-        return -1;
+        return _Py_INIT_ERR("unable to load the file system codec");
     }
     Py_DECREF(codec);
     interp->fscodec_initialized = 1;
-    return 0;
+    return _Py_INIT_OK();
 }
 
 /* Import the site module (not into __main__ though) */
 
-static void
+static _PyInitError
 initsite(void)
 {
     PyObject *m;
     m = PyImport_ImportModule("site");
     if (m == NULL) {
-        fprintf(stderr, "Failed to import the site module\n");
-        PyErr_Print();
-        Py_Finalize();
-        exit(1);
-    }
-    else {
-        Py_DECREF(m);
+        return _Py_INIT_USER_ERR("Failed to import the site module");
     }
+    Py_DECREF(m);
+    return _Py_INIT_OK();
 }
 
 /* Check if a file descriptor is valid or not.
@@ -1926,8 +2022,8 @@ fatal_output_debug(const char *msg)
 }
 #endif
 
-void
-Py_FatalError(const char *msg)
+static void
+fatal_error(const char *prefix, const char *msg, int status)
 {
     const int fd = fileno(stderr);
     static int reentrant = 0;
@@ -1939,7 +2035,18 @@ Py_FatalError(const char *msg)
     }
     reentrant = 1;
 
-    fprintf(stderr, "Fatal Python error: %s\n", msg);
+    fprintf(stderr, "Fatal Python error: ");
+    if (prefix) {
+        fputs(prefix, stderr);
+        fputs(": ", stderr);
+    }
+    if (msg) {
+        fputs(msg, stderr);
+    }
+    else {
+        fprintf(stderr, "<message not set>");
+    }
+    fputs("\n", stderr);
     fflush(stderr); /* it helps in Windows debug build */
 
     /* Print the exception (if an exception is set) with its traceback,
@@ -1965,10 +2072,30 @@ Py_FatalError(const char *msg)
 #endif /* MS_WINDOWS */
 
 exit:
+    if (status < 0) {
 #if defined(MS_WINDOWS) && defined(_DEBUG)
-    DebugBreak();
+        DebugBreak();
 #endif
-    abort();
+        abort();
+    }
+    else {
+        exit(status);
+    }
+}
+
+void
+Py_FatalError(const char *msg)
+{
+    fatal_error(NULL, msg, -1);
+}
+
+void
+_Py_FatalInitError(_PyInitError err)
+{
+    /* On "user" error: exit with status 1.
+       For all other errors, call abort(). */
+    int status = err.user_err ? 1 : -1;
+    fatal_error(err.prefix, err.msg, status);
 }
 
 /* Clean up and exit */
@@ -2045,7 +2172,7 @@ Py_Exit(int sts)
     exit(sts);
 }
 
-static void
+static _PyInitError
 initsigs(void)
 {
 #ifdef SIGPIPE
@@ -2059,8 +2186,9 @@ initsigs(void)
 #endif
     PyOS_InitInterrupts(); /* May imply initsignal() */
     if (PyErr_Occurred()) {
-        Py_FatalError("Py_Initialize: can't import signal");
+        return _Py_INIT_ERR("can't import signal");
     }
+    return _Py_INIT_OK();
 }
 
 
diff --git a/Python/pystate.c b/Python/pystate.c
index 55ff64951b0..30f214e93d6 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -35,7 +35,7 @@ to avoid the expense of doing their own locking).
 extern "C" {
 #endif
 
-void
+_PyInitError
 _PyRuntimeState_Init(_PyRuntimeState *runtime)
 {
     memset(runtime, 0, sizeof(*runtime));
@@ -46,17 +46,19 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
     _PyEval_Initialize(&runtime->ceval);
 
     runtime->gilstate.check_enabled = 1;
+
     /* A TSS key must be initialized with Py_tss_NEEDS_INIT
        in accordance with the specification. */
-    {
-        Py_tss_t initial = Py_tss_NEEDS_INIT;
-        runtime->gilstate.autoTSSkey = initial;
-    }
+    Py_tss_t initial = Py_tss_NEEDS_INIT;
+    runtime->gilstate.autoTSSkey = initial;
 
     runtime->interpreters.mutex = PyThread_allocate_lock();
-    if (runtime->interpreters.mutex == NULL)
-        Py_FatalError("Can't initialize threads for interpreter");
+    if (runtime->interpreters.mutex == NULL) {
+        return _Py_INIT_ERR("Can't initialize threads for interpreter");
+    }
+
     runtime->interpreters.next_id = -1;
+    return _Py_INIT_OK();
 }
 
 void
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 6dc8e08be7d..d786739188a 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -1582,13 +1582,23 @@ PySys_ResetWarnOptions(void)
     PyList_SetSlice(warnoptions, 0, PyList_GET_SIZE(warnoptions), NULL);
 }
 
-void
-PySys_AddWarnOptionUnicode(PyObject *unicode)
+int
+_PySys_AddWarnOptionWithError(PyObject *option)
 {
     PyObject *warnoptions = get_warnoptions();
-    if (warnoptions == NULL)
-        return;
-    PyList_Append(warnoptions, unicode);
+    if (warnoptions == NULL) {
+        return -1;
+    }
+    if (PyList_Append(warnoptions, option)) {
+        return -1;
+    }
+    return 0;
+}
+
+void
+PySys_AddWarnOptionUnicode(PyObject *option)
+{
+    (void)_PySys_AddWarnOptionWithError(option);
 }
 
 void
@@ -1627,18 +1637,17 @@ get_xoptions(void)
     return xoptions;
 }
 
-void
-PySys_AddXOption(const wchar_t *s)
+int
+_PySys_AddXOptionWithError(const wchar_t *s)
 {
-    PyObject *opts;
     PyObject *name = NULL, *value = NULL;
-    const wchar_t *name_end;
 
-    opts = get_xoptions();
-    if (opts == NULL)
+    PyObject *opts = get_xoptions();
+    if (opts == NULL) {
         goto error;
+    }
 
-    name_end = wcschr(s, L'=');
+    const wchar_t *name_end = wcschr(s, L'=');
     if (!name_end) {
         name = PyUnicode_FromWideChar(s, -1);
         value = Py_True;
@@ -1648,19 +1657,30 @@ PySys_AddXOption(const wchar_t *s)
         name = PyUnicode_FromWideChar(s, name_end - s);
         value = PyUnicode_FromWideChar(name_end + 1, -1);
     }
-    if (name == NULL || value == NULL)
+    if (name == NULL || value == NULL) {
+        goto error;
+    }
+    if (PyDict_SetItem(opts, name, value) < 0) {
         goto error;
-    PyDict_SetItem(opts, name, value);
+    }
     Py_DECREF(name);
     Py_DECREF(value);
-    return;
+    return 0;
 
 error:
     Py_XDECREF(name);
     Py_XDECREF(value);
-    /* No return value, therefore clear error state if possible */
-    if (_PyThreadState_UncheckedGet()) {
-        PyErr_Clear();
+    return -1;
+}
+
+void
+PySys_AddXOption(const wchar_t *s)
+{
+    if (_PySys_AddXOptionWithError(s) < 0) {
+        /* No return value, therefore clear error state if possible */
+        if (_PyThreadState_UncheckedGet()) {
+            PyErr_Clear();
+        }
     }
 }
 
@@ -1999,50 +2019,52 @@ static struct PyModuleDef sysmodule = {
 #define SET_SYS_FROM_STRING_BORROW(key, value)             \
     do {                                                   \
         PyObject *v = (value);                             \
-        if (v == NULL)                                     \
-            return NULL;                                   \
+        if (v == NULL) {                                   \
+            goto err_occurred;                             \
+        }                                                  \
         res = PyDict_SetItemString(sysdict, key, v);       \
         if (res < 0) {                                     \
-            return NULL;                                   \
+            goto err_occurred;                             \
         }                                                  \
     } while (0)
 #define SET_SYS_FROM_STRING(key, value)                    \
     do {                                                   \
         PyObject *v = (value);                             \
-        if (v == NULL)                                     \
-            return NULL;                                   \
+        if (v == NULL) {                                   \
+            goto err_occurred;                             \
+        }                                                  \
         res = PyDict_SetItemString(sysdict, key, v);       \
         Py_DECREF(v);                                      \
         if (res < 0) {                                     \
-            return NULL;                                   \
+            goto err_occurred;                             \
         }                                                  \
     } while (0)
 
-PyObject *
-_PySys_BeginInit(void)
+
+_PyInitError
+_PySys_BeginInit(PyObject **sysmod)
 {
     PyObject *m, *sysdict, *version_info;
     int res;
 
     m = _PyModule_CreateInitialized(&sysmodule, PYTHON_API_VERSION);
-    if (m == NULL)
-        return NULL;
+    if (m == NULL) {
+        return _Py_INIT_ERR("failed to create a module object");
+    }
     sysdict = PyModule_GetDict(m);
 
     /* Check that stdin is not a directory
-    Using shell redirection, you can redirect stdin to a directory,
-    crashing the Python interpreter. Catch this common mistake here
-    and output a useful error message. Note that under MS Windows,
-    the shell already prevents that. */
-#if !defined(MS_WINDOWS)
+       Using shell redirection, you can redirect stdin to a directory,
+       crashing the Python interpreter. Catch this common mistake here
+       and output a useful error message. Note that under MS Windows,
+       the shell already prevents that. */
+#ifndef MS_WINDOWS
     {
         struct _Py_stat_struct sb;
         if (_Py_fstat_noraise(fileno(stdin), &sb) == 0 &&
             S_ISDIR(sb.st_mode)) {
-            /* There's nothing more we can do. */
-            /* Py_FatalError() will core dump, so just exit. */
-            PySys_WriteStderr("Python error: <stdin> is a directory, cannot continue\n");
-            exit(EXIT_FAILURE);
+            return _Py_INIT_USER_ERR("<stdin> is a directory, "
+                                     "cannot continue");
         }
     }
 #endif
@@ -2078,8 +2100,9 @@ _PySys_BeginInit(void)
                         PyLong_GetInfo());
     /* initialize hash_info */
     if (Hash_InfoType.tp_name == NULL) {
-        if (PyStructSequence_InitType2(&Hash_InfoType, &hash_info_desc) < 0)
-            return NULL;
+        if (PyStructSequence_InitType2(&Hash_InfoType, &hash_info_desc) < 0) {
+            goto type_init_failed;
+        }
     }
     SET_SYS_FROM_STRING("hash_info",
                         get_hash_info());
@@ -2109,8 +2132,9 @@ _PySys_BeginInit(void)
     /* version_info */
     if (VersionInfoType.tp_name == NULL) {
         if (PyStructSequence_InitType2(&VersionInfoType,
-                                       &version_info_desc) < 0)
-            return NULL;
+                                       &version_info_desc) < 0) {
+            goto type_init_failed;
+        }
     }
     version_info = make_version_info();
     SET_SYS_FROM_STRING("version_info", version_info);
@@ -2126,8 +2150,9 @@ _PySys_BeginInit(void)
 
     /* flags */
     if (FlagsType.tp_name == 0) {
-        if (PyStructSequence_InitType2(&FlagsType, &flags_desc) < 0)
-            return NULL;
+        if (PyStructSequence_InitType2(&FlagsType, &flags_desc) < 0) {
+            goto type_init_failed;
+        }
     }
     /* Set flags to their default values */
     SET_SYS_FROM_STRING("flags", make_flags());
@@ -2136,14 +2161,17 @@ _PySys_BeginInit(void)
     /* getwindowsversion */
     if (WindowsVersionType.tp_name == 0)
         if (PyStructSequence_InitType2(&WindowsVersionType,
-                                       &windows_version_desc) < 0)
-            return NULL;
+                                       &windows_version_desc) < 0) {
+            goto type_init_failed;
+        }
     /* prevent user from creating new instances */
     WindowsVersionType.tp_init = NULL;
     WindowsVersionType.tp_new = NULL;
+    assert(!PyErr_Occurred());
     res = PyDict_DelItemString(WindowsVersionType.tp_dict, "__new__");
-    if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
+    if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
         PyErr_Clear();
+    }
 #endif
 
     /* float repr style: 0.03 (short) vs 0.029999999999999999 (legacy) */
@@ -2161,13 +2189,22 @@ _PySys_BeginInit(void)
     if (AsyncGenHooksType.tp_name == NULL) {
         if (PyStructSequence_InitType2(
                 &AsyncGenHooksType, &asyncgen_hooks_desc) < 0) {
-            return NULL;
+            goto type_init_failed;
         }
     }
 
-    if (PyErr_Occurred())
-        return NULL;
-    return m;
+    if (PyErr_Occurred()) {
+        goto err_occurred;
+    }
+
+    *sysmod = m;
+    return _Py_INIT_OK();
+
+type_init_failed:
+    return _Py_INIT_ERR("failed to initialize a type");
+
+err_occurred:
+    return _Py_INIT_ERR("can't initialize sys module");
 }
 
 #undef SET_SYS_FROM_STRING



More information about the Python-checkins mailing list