[Python-checkins] bpo-45445: Fail if an invalid X-option is provided in the command line (GH-28823)

pablogsal webhook-mailer at python.org
Wed Oct 13 13:08:27 EDT 2021


https://github.com/python/cpython/commit/db2b6a20cd35781b2f5e798e880e57e6cf9b97aa
commit: db2b6a20cd35781b2f5e798e880e57e6cf9b97aa
branch: main
author: Pablo Galindo Salgado <Pablogsal at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-10-13T18:08:19+01:00
summary:

bpo-45445: Fail if an invalid X-option is provided in the command line (GH-28823)

files:
A Misc/NEWS.d/next/Core and Builtins/2021-10-12-14-41-39.bpo-45445._F5cMf.rst
A test_foo.py
M Doc/library/sys.rst
M Lib/test/test_audit.py
M Lib/test/test_cmd_line.py
M Lib/test/test_embed.py
M Programs/_testembed.c
M Python/initconfig.c

diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 8b3c6fd762731..ee07ba1042f62 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -1729,13 +1729,13 @@ always available.
 
    .. code-block:: shell-session
 
-      $ ./python -Xa=b -Xc
+      $ ./python -Xpycache_prefix=some_path -Xdev
       Python 3.2a3+ (py3k, Oct 16 2010, 20:14:50)
       [GCC 4.4.3] on linux2
       Type "help", "copyright", "credits" or "license" for more information.
       >>> import sys
       >>> sys._xoptions
-      {'a': 'b', 'c': True}
+      {'pycache_prefix': 'some_path', 'dev': True}
 
    .. impl-detail::
 
diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py
index c5ce26323b5f9..d99b3b7ed7d36 100644
--- a/Lib/test/test_audit.py
+++ b/Lib/test/test_audit.py
@@ -18,7 +18,7 @@
 class AuditTest(unittest.TestCase):
     def do_test(self, *args):
         with subprocess.Popen(
-            [sys.executable, "-X utf8", AUDIT_TESTS_PY, *args],
+            [sys.executable, "-Xutf8", AUDIT_TESTS_PY, *args],
             encoding="utf-8",
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
@@ -32,7 +32,7 @@ def do_test(self, *args):
     def run_python(self, *args):
         events = []
         with subprocess.Popen(
-            [sys.executable, "-X utf8", AUDIT_TESTS_PY, *args],
+            [sys.executable, "-Xutf8", AUDIT_TESTS_PY, *args],
             encoding="utf-8",
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index d93e98f372532..1dc8c45885cbe 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -83,8 +83,17 @@ def get_xoptions(*args):
         opts = get_xoptions()
         self.assertEqual(opts, {})
 
-        opts = get_xoptions('-Xa', '-Xb=c,d=e')
-        self.assertEqual(opts, {'a': True, 'b': 'c,d=e'})
+        opts = get_xoptions('-Xno_debug_ranges', '-Xdev=1234')
+        self.assertEqual(opts, {'no_debug_ranges': True, 'dev': '1234'})
+
+    @unittest.skipIf(interpreter_requires_environment(),
+                     'Cannot run -E tests when PYTHON env vars are required.')
+    def test_unknown_xoptions(self):
+        rc, out, err = assert_python_failure('-X', 'blech')
+        self.assertIn(b'Unknown value for option -X', err)
+        msg = b'Fatal Python error: Unknown value for option -X'
+        self.assertEqual(err.splitlines().count(msg), 1)
+        self.assertEqual(b'', out)
 
     def test_showrefcount(self):
         def run_python(*args):
diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 31c5c3e49dda1..41e092019c49a 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -273,7 +273,7 @@ def test_pre_initialization_sys_options(self):
                         "test_pre_initialization_sys_options", env=env)
         expected_output = (
             "sys.warnoptions: ['once', 'module', 'default']\n"
-            "sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n"
+            "sys._xoptions: {'dev': '2', 'utf8': '1'}\n"
             "warnings.filters[:3]: ['default', 'module', 'once']\n"
         )
         self.assertIn(expected_output, out)
@@ -820,15 +820,14 @@ def test_init_from_config(self):
             'argv': ['-c', 'arg2'],
             'orig_argv': ['python3',
                           '-W', 'cmdline_warnoption',
-                          '-X', 'cmdline_xoption',
+                          '-X', 'dev',
                           '-c', 'pass',
                           'arg2'],
             'parse_argv': 2,
             'xoptions': [
-                'config_xoption1=3',
-                'config_xoption2=',
-                'config_xoption3',
-                'cmdline_xoption',
+                'dev=3',
+                'utf8',
+                'dev',
             ],
             'warnoptions': [
                 'cmdline_warnoption',
@@ -1046,9 +1045,8 @@ def test_init_sys_add(self):
         config = {
             'faulthandler': 1,
             'xoptions': [
-                'config_xoption',
-                'cmdline_xoption',
-                'sysadd_xoption',
+                'dev',
+                'utf8',
                 'faulthandler',
             ],
             'warnoptions': [
@@ -1058,9 +1056,12 @@ def test_init_sys_add(self):
             ],
             'orig_argv': ['python3',
                           '-W', 'ignore:::cmdline_warnoption',
-                          '-X', 'cmdline_xoption'],
+                          '-X', 'utf8'],
         }
-        self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
+        preconfig = {'utf8_mode': 1}
+        self.check_all_configs("test_init_sys_add", config,
+                               expected_preconfig=preconfig,
+                               api=API_PYTHON)
 
     def test_init_run_main(self):
         code = ('import _testinternalcapi, json; '
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-10-12-14-41-39.bpo-45445._F5cMf.rst b/Misc/NEWS.d/next/Core and Builtins/2021-10-12-14-41-39.bpo-45445._F5cMf.rst
new file mode 100644
index 0000000000000..d497ae26bd577
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-10-12-14-41-39.bpo-45445._F5cMf.rst	
@@ -0,0 +1,2 @@
+Python now fails to initialize if it finds an invalid :option:`-X` option in the
+command line. Patch by Pablo Galindo.
diff --git a/Programs/_testembed.c b/Programs/_testembed.c
index 7628e1a17d9ff..fa418e276114a 100644
--- a/Programs/_testembed.c
+++ b/Programs/_testembed.c
@@ -211,7 +211,7 @@ static int test_pre_initialization_sys_options(void)
      * relying on the caller to keep the passed in strings alive.
      */
     const wchar_t *static_warnoption = L"once";
-    const wchar_t *static_xoption = L"also_not_an_option=2";
+    const wchar_t *static_xoption = L"utf8=1";
     size_t warnoption_len = wcslen(static_warnoption);
     size_t xoption_len = wcslen(static_xoption);
     wchar_t *dynamic_once_warnoption = \
@@ -230,7 +230,7 @@ static int test_pre_initialization_sys_options(void)
     PySys_AddWarnOption(L"module");
     PySys_AddWarnOption(L"default");
     _Py_EMBED_PREINIT_CHECK("Checking PySys_AddXOption\n");
-    PySys_AddXOption(L"not_an_option=1");
+    PySys_AddXOption(L"dev=2");
     PySys_AddXOption(dynamic_xoption);
 
     /* Delete the dynamic options early */
@@ -548,7 +548,7 @@ static int test_init_from_config(void)
         L"-W",
         L"cmdline_warnoption",
         L"-X",
-        L"cmdline_xoption",
+        L"dev",
         L"-c",
         L"pass",
         L"arg2",
@@ -556,10 +556,9 @@ static int test_init_from_config(void)
     config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
     config.parse_argv = 1;
 
-    wchar_t* xoptions[3] = {
-        L"config_xoption1=3",
-        L"config_xoption2=",
-        L"config_xoption3",
+    wchar_t* xoptions[2] = {
+        L"dev=3",
+        L"utf8",
     };
     config_set_wide_string_list(&config, &config.xoptions,
                                 Py_ARRAY_LENGTH(xoptions), xoptions);
@@ -1375,7 +1374,6 @@ static int test_init_read_set(void)
 
 static int test_init_sys_add(void)
 {
-    PySys_AddXOption(L"sysadd_xoption");
     PySys_AddXOption(L"faulthandler");
     PySys_AddWarnOption(L"ignore:::sysadd_warnoption");
 
@@ -1387,14 +1385,14 @@ static int test_init_sys_add(void)
         L"-W",
         L"ignore:::cmdline_warnoption",
         L"-X",
-        L"cmdline_xoption",
+        L"utf8",
     };
     config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
     config.parse_argv = 1;
 
     PyStatus status;
     status = PyWideStringList_Append(&config.xoptions,
-                                     L"config_xoption");
+                                     L"dev");
     if (PyStatus_Exception(status)) {
         goto fail;
     }
diff --git a/Python/initconfig.c b/Python/initconfig.c
index b91d280906cbe..ba6d19db20048 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -2129,6 +2129,49 @@ _PyConfig_InitImportConfig(PyConfig *config)
     return config_init_import(config, 1);
 }
 
+// List of known xoptions to validate against the provided ones. Note that all
+// options are listed, even if they are only available if a specific macro is
+// set, like -X showrefcount which requires a debug build. In this case unknown
+// options are silently ignored.
+const wchar_t* known_xoptions[] = {
+    L"faulthandler",
+    L"showrefcount",
+    L"tracemalloc",
+    L"importtime",
+    L"dev",
+    L"utf8",
+    L"pycache_prefix",
+    L"warn_default_encoding",
+    L"no_debug_ranges",
+    L"frozen_modules",
+    NULL,
+};
+
+static const wchar_t*
+_Py_check_xoptions(const PyWideStringList *xoptions, const wchar_t **names)
+{
+    for (Py_ssize_t i=0; i < xoptions->length; i++) {
+        const wchar_t *option = xoptions->items[i];
+        size_t len;
+        wchar_t *sep = wcschr(option, L'=');
+        if (sep != NULL) {
+            len = (sep - option);
+        }
+        else {
+            len = wcslen(option);
+        }
+        int found = 0;
+        for (const wchar_t** name = names; *name != NULL; name++) {
+            if (wcsncmp(option, *name, len) == 0 && (*name)[len] == L'\0') {
+                found = 1;
+            }
+        }
+        if (found == 0) {
+            return option;
+        }
+    }
+    return NULL;
+}
 
 static PyStatus
 config_read(PyConfig *config, int compute_path_config)
@@ -2144,6 +2187,11 @@ config_read(PyConfig *config, int compute_path_config)
     }
 
     /* -X options */
+    const wchar_t* option = _Py_check_xoptions(&config->xoptions, known_xoptions);
+    if (option != NULL) {
+        return PyStatus_Error("Unknown value for option -X");
+    }
+
     if (config_get_xoption(config, L"showrefcount")) {
         config->show_ref_count = 1;
     }
diff --git a/test_foo.py b/test_foo.py
new file mode 100644
index 0000000000000..a27be0fdb47a6
--- /dev/null
+++ b/test_foo.py
@@ -0,0 +1,3 @@
+def foo(a=3, *, c, d=2):
+    pass
+foo()



More information about the Python-checkins mailing list