diff options
author | Victor Stinner <victor.stinner@gmail.com> | 2017-11-15 23:48:08 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-15 23:48:08 (GMT) |
commit | f7e5b56c37eb859e225e886c79c5d742c567ee95 (patch) | |
tree | 7d722ca38595aaa68e02a1ee1ea53e17a54b0188 /Modules/main.c | |
parent | 43605e6bfa8d49612df4a38460d063d6ba781906 (diff) | |
download | cpython-f7e5b56c37eb859e225e886c79c5d742c567ee95.zip cpython-f7e5b56c37eb859e225e886c79c5d742c567ee95.tar.gz cpython-f7e5b56c37eb859e225e886c79c5d742c567ee95.tar.bz2 |
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)
Diffstat (limited to 'Modules/main.c')
-rw-r--r-- | Modules/main.c | 1122 |
1 files changed, 778 insertions, 344 deletions
diff --git a/Modules/main.c b/Modules/main.c index 54abbcc..6391ba7 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 @@ error: } -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 @@ error: 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 @@ error: 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 |