From c6689f64648518d2d242bc877e4b910b5d75b34c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 28 Sep 2026 18:55:19 +0200 Subject: [PATCH] Cleanup Python/getopt.c: add _PyOS_GetOpt structure Replace global variables with a new _PyOS_GetOpt structure: * _PyOS_opterr: getopt->error * _PyOS_optind: getopt->index * _PyOS_optarg: getopt->arg Changes: * Rename _PyOS_ResetGetOpt() to _PyOS_GetOpt_Init(). Add argc and argv parameters. * Don't expose longindex in the structure, it's only used internally. * Move _PyOS_LongOption structure from the internal C API to getopt.c. --- Include/internal/pycore_getopt.h | 25 ++++--- Python/getopt.c | 117 ++++++++++++++++--------------- Python/initconfig.c | 34 ++++----- Python/preconfig.c | 10 +-- 4 files changed, 98 insertions(+), 88 deletions(-) diff --git a/Include/internal/pycore_getopt.h b/Include/internal/pycore_getopt.h index 7f0dd13ae577f7..e767cb1049d079 100644 --- a/Include/internal/pycore_getopt.h +++ b/Include/internal/pycore_getopt.h @@ -5,18 +5,21 @@ # error "this header requires Py_BUILD_CORE define" #endif -extern int _PyOS_opterr; -extern Py_ssize_t _PyOS_optind; -extern const wchar_t *_PyOS_optarg; +struct _PyOS_GetOpt { + int error; // generate error messages + Py_ssize_t index; // index into argv array + const wchar_t *arg; // optional argument + const wchar_t *ptr; + Py_ssize_t argc; + wchar_t * const *argv; +}; -extern void _PyOS_ResetGetOpt(void); +extern void _PyOS_GetOpt_Init( + struct _PyOS_GetOpt *getopt, + Py_ssize_t argc, + wchar_t * const *argv); -typedef struct { - const wchar_t *name; - int has_arg; - int val; -} _PyOS_LongOption; - -extern int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex); +extern int _PyOS_GetOpt( + struct _PyOS_GetOpt *getopt); #endif /* !Py_INTERNAL_PYGETOPT_H */ diff --git a/Python/getopt.c b/Python/getopt.c index 7e918189c716a9..074069b4b7b9c7 100644 --- a/Python/getopt.c +++ b/Python/getopt.c @@ -27,18 +27,19 @@ #include #include #include -#include "pycore_getopt.h" +#include "pycore_getopt.h" // struct _PyOS_GetOpt -int _PyOS_opterr = 1; /* generate error messages */ -Py_ssize_t _PyOS_optind = 1; /* index into argv array */ -const wchar_t *_PyOS_optarg = NULL; /* optional argument */ - -static const wchar_t *opt_ptr = L""; /* Python command line short and long options */ #define SHORT_OPTS L"bBc:dEhiIm:OPqRsStuvVW:xX:?" +typedef struct { + const wchar_t *name; + int has_arg; + int val; +} _PyOS_LongOption; + static const _PyOS_LongOption longopts[] = { /* name, has_arg, val (used in switch in initconfig.c) */ {L"check-hash-based-pycs", 1, 1}, @@ -49,113 +50,119 @@ static const _PyOS_LongOption longopts[] = { }; -void _PyOS_ResetGetOpt(void) +void +_PyOS_GetOpt_Init(struct _PyOS_GetOpt *getopt, + Py_ssize_t argc, wchar_t * const *argv) { - _PyOS_opterr = 1; - _PyOS_optind = 1; - _PyOS_optarg = NULL; - opt_ptr = L""; + getopt->error = 1; + getopt->index = 1; + getopt->arg = NULL; + getopt->ptr = L""; + getopt->argc = argc; + getopt->argv = argv; } -int _PyOS_GetOpt(Py_ssize_t argc, wchar_t * const *argv, int *longindex) +int +_PyOS_GetOpt(struct _PyOS_GetOpt *getopt) { - wchar_t *ptr; - wchar_t option; - - if (*opt_ptr == '\0') { + if (*getopt->ptr == '\0') { + if (getopt->index >= getopt->argc) { + return -1; + } - if (_PyOS_optind >= argc) + const wchar_t *arg = getopt->argv[getopt->index]; + if (arg[0] != L'-' || arg[1] == L'\0' /* lone dash */ ) { return -1; + } + #ifdef MS_WINDOWS - else if (wcscmp(argv[_PyOS_optind], L"/?") == 0) { - ++_PyOS_optind; + if (wcscmp(arg, L"/?") == 0) { + ++getopt->index; return 'h'; } #endif - - else if (argv[_PyOS_optind][0] != L'-' || - argv[_PyOS_optind][1] == L'\0' /* lone dash */ ) - return -1; - - else if (wcscmp(argv[_PyOS_optind], L"--") == 0) { - ++_PyOS_optind; + if (wcscmp(arg, L"--") == 0) { + ++getopt->index; return -1; } - - else if (wcscmp(argv[_PyOS_optind], L"--help") == 0) { - ++_PyOS_optind; + if (wcscmp(arg, L"--help") == 0) { + ++getopt->index; return 'h'; } - - else if (wcscmp(argv[_PyOS_optind], L"--version") == 0) { - ++_PyOS_optind; + if (wcscmp(arg, L"--version") == 0) { + ++getopt->index; return 'V'; } - opt_ptr = &argv[_PyOS_optind++][1]; + getopt->ptr = &getopt->argv[getopt->index++][1]; } - if ((option = *opt_ptr++) == L'\0') + wchar_t option = *getopt->ptr++; + if (option == L'\0') { return -1; + } if (option == L'-') { // Parse long option. - if (*opt_ptr == L'\0') { - if (_PyOS_opterr) { + if (*getopt->ptr == L'\0') { + if (getopt->error) { fprintf(stderr, "Expected long option\n"); } return -1; } - *longindex = 0; + int longindex = 0; const _PyOS_LongOption *opt; - for (opt = &longopts[*longindex]; opt->name; opt = &longopts[++(*longindex)]) { - if (!wcscmp(opt->name, opt_ptr)) + for (opt = &longopts[longindex]; opt->name; opt = &longopts[++longindex]) { + if (wcscmp(opt->name, getopt->ptr) == 0) { break; + } } + if (!opt->name) { - if (_PyOS_opterr) { - fprintf(stderr, "Unknown option: %ls\n", argv[_PyOS_optind - 1]); + if (getopt->error) { + fprintf(stderr, "Unknown option: %ls\n", getopt->argv[getopt->index - 1]); } return '_'; } - opt_ptr = L""; + + getopt->ptr = L""; if (!opt->has_arg) { return opt->val; } - if (_PyOS_optind >= argc) { - if (_PyOS_opterr) { + if (getopt->index >= getopt->argc) { + if (getopt->error) { fprintf(stderr, "Argument expected for the %ls options\n", - argv[_PyOS_optind - 1]); + getopt->argv[getopt->index - 1]); } return '_'; } - _PyOS_optarg = argv[_PyOS_optind++]; + getopt->arg = getopt->argv[getopt->index++]; return opt->val; } - if ((ptr = wcschr(SHORT_OPTS, option)) == NULL) { - if (_PyOS_opterr) { + wchar_t *ptr = wcschr(SHORT_OPTS, option); + if (ptr == NULL) { + if (getopt->error) { fprintf(stderr, "Unknown option: -%c\n", (char)option); } return '_'; } if (*(ptr + 1) == L':') { - if (*opt_ptr != L'\0') { - _PyOS_optarg = opt_ptr; - opt_ptr = L""; + if (*getopt->ptr != L'\0') { + getopt->arg = getopt->ptr; + getopt->ptr = L""; } - else { - if (_PyOS_optind >= argc) { - if (_PyOS_opterr) { + if (getopt->index >= getopt->argc) { + if (getopt->error) { fprintf(stderr, "Argument expected for the -%c option\n", (char)option); } return '_'; } - _PyOS_optarg = argv[_PyOS_optind++]; + getopt->arg = getopt->argv[getopt->index++]; } } diff --git a/Python/initconfig.c b/Python/initconfig.c index 6de12db9d600ec..b245cabfb689e0 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -3009,10 +3009,10 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, const PyWideStringList *argv = &config->argv; int print_version = 0; - _PyOS_ResetGetOpt(); + struct _PyOS_GetOpt getopt; + _PyOS_GetOpt_Init(&getopt, argv->length, argv->items); do { - int longindex = -1; - int c = _PyOS_GetOpt(argv->length, argv->items, &longindex); + int c = _PyOS_GetOpt(&getopt); if (c == EOF) { break; } @@ -3022,12 +3022,12 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, /* -c is the last option; following arguments that look like options are left for the command to interpret. */ - size_t len = wcslen(_PyOS_optarg) + 1 + 1; + size_t len = wcslen(getopt.arg) + 1 + 1; wchar_t *command = PyMem_RawMalloc(sizeof(wchar_t) * len); if (command == NULL) { return _PyStatus_NO_MEMORY(); } - memcpy(command, _PyOS_optarg, (len - 2) * sizeof(wchar_t)); + memcpy(command, getopt.arg, (len - 2) * sizeof(wchar_t)); command[len - 2] = '\n'; command[len - 1] = 0; config->run_command = command; @@ -3040,7 +3040,7 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, that look like options are left for the module to interpret. */ if (config->run_module == NULL) { - config->run_module = _PyMem_RawWcsdup(_PyOS_optarg); + config->run_module = _PyMem_RawWcsdup(getopt.arg); if (config->run_module == NULL) { return _PyStatus_NO_MEMORY(); } @@ -3052,12 +3052,12 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, // Integers represent long options, see Python/getopt.c case 1: // check-hash-based-pycs - if (wcscmp(_PyOS_optarg, L"always") == 0 - || wcscmp(_PyOS_optarg, L"never") == 0 - || wcscmp(_PyOS_optarg, L"default") == 0) + if (wcscmp(getopt.arg, L"always") == 0 + || wcscmp(getopt.arg, L"never") == 0 + || wcscmp(getopt.arg, L"default") == 0) { status = PyConfig_SetString(config, &config->check_hash_pycs_mode, - _PyOS_optarg); + getopt.arg); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -3146,7 +3146,7 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, break; case 'W': - status = PyWideStringList_Append(warnoptions, _PyOS_optarg); + status = PyWideStringList_Append(warnoptions, getopt.arg); if (_PyStatus_EXCEPTION(status)) { return status; } @@ -3177,22 +3177,22 @@ config_parse_cmdline(PyConfig *config, PyWideStringList *warnoptions, } if (config->run_command == NULL && config->run_module == NULL - && _PyOS_optind < argv->length - && wcscmp(argv->items[_PyOS_optind], L"-") != 0 + && getopt.index < argv->length + && wcscmp(argv->items[getopt.index], L"-") != 0 && config->run_filename == NULL) { - config->run_filename = _PyMem_RawWcsdup(argv->items[_PyOS_optind]); + config->run_filename = _PyMem_RawWcsdup(argv->items[getopt.index]); if (config->run_filename == NULL) { return _PyStatus_NO_MEMORY(); } } if (config->run_command != NULL || config->run_module != NULL) { - /* Backup _PyOS_optind */ - _PyOS_optind--; + /* Backup getopt.index */ + getopt.index--; } - *opt_index = _PyOS_optind; + *opt_index = getopt.index; return _PyStatus_OK(); diff --git a/Python/preconfig.c b/Python/preconfig.c index 844ac8e6372fc5..8142fe55930dfe 100644 --- a/Python/preconfig.c +++ b/Python/preconfig.c @@ -187,13 +187,13 @@ precmdline_parse_cmdline(_PyPreCmdline *cmdline) { const PyWideStringList *argv = &cmdline->argv; - _PyOS_ResetGetOpt(); + struct _PyOS_GetOpt getopt; + _PyOS_GetOpt_Init(&getopt, argv->length, argv->items); /* Don't log parsing errors into stderr here: PyConfig_Read() is responsible for that */ - _PyOS_opterr = 0; + getopt.error = 0; do { - int longindex = -1; - int c = _PyOS_GetOpt(argv->length, argv->items, &longindex); + int c = _PyOS_GetOpt(&getopt); if (c == EOF || c == 'c' || c == 'm') { break; @@ -211,7 +211,7 @@ precmdline_parse_cmdline(_PyPreCmdline *cmdline) case 'X': { PyStatus status = PyWideStringList_Append(&cmdline->xoptions, - _PyOS_optarg); + getopt.arg); if (_PyStatus_EXCEPTION(status)) { return status; }