8000 bpo-45445: Fail if an invalid X-option is provided in the command lin… · python/cpython@db2b6a2 · GitHub
[go: up one dir, main page]

Skip to content

Commit db2b6a2

Browse files
authored
bpo-45445: Fail if an invalid X-option is provided in the command line (GH-28823)
1 parent 1c83135 commit db2b6a2

File tree

8 files changed

+88
-27
lines changed

8 files changed

+88
-27
lines changed

Doc/library/sys.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,13 +1729,13 @@ always available.
17291729

17301730
.. code-block:: shell-session
17311731
1732-
$ ./python -Xa=b -Xc
1732+
$ ./python -Xpycache_prefix=some_path -Xdev
17331733
Python 3.2a3+ (py3k, Oct 16 2010, 20:14:50)
17341734
[GCC 4.4.3] on linux2
17351735
Type "help", "copyright", "credits" or "license" for more information.
17361736
>>> import sys
17371737
>>> sys._xoptions
1738-
{'a': 'b', 'c': True}
1738+
{'pycache_prefix': 'some_path', 'dev': True}
17391739
17401740
.. impl-detail::
17411741

Lib/test/test_audit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
class AuditTest(unittest.TestCase):
1919
def do_test(self, *args):
2020
with subprocess.Popen(
21-
[sys.executable, "-X utf8", AUDIT_TESTS_PY, *args],
21+
[sys.executable, "-Xutf8", AUDIT_TESTS_PY, *args],
2222
encoding="utf-8",
2323
stdout=subprocess.PIPE,
2424
stderr=subprocess.PIPE,
@@ -32,7 +32,7 @@ def do_test(self, *args):
3232
def run_python(self, *args):
3333
events = []
3434
with subprocess.Popen(
35-
[sys.executable, "-X utf8", AUDIT_TESTS_PY, *args],
35+
[sys.executable, "-Xutf8", AUDIT_TESTS_PY, *args],
3636
encoding="utf-8",
3737
stdout=subprocess.PIPE,
3838
stderr=subprocess.PIPE,

Lib/test/test_cmd_line.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,17 @@ def get_xoptions(*args):
8383
opts = get_xoptions()
8484
self.assertEqual(opts, {})
8585

86-
opts = get_xoptions('-Xa', '-Xb=c,d=e')
87-
self.assertEqual(opts, {'a': True, 'b': 'c,d=e'})
86+
opts = get_xoptions('-Xno_debug_ranges', '-Xdev=1234')
87+
self.assertEqual(opts, {'no_debug_ranges': True, 'dev': '1234'})
88+
89+
@unittest.skipIf(interpreter_requires_environment(),
90+
'Cannot run -E tests when PYTHON env vars are required.')
91+
def test_unknown_xoptions(self):
92+
rc, out, err = assert_python_failure('-X', 'blech')
93+
self.assertIn(b'Unknown value for option -X', err)
94+
msg = b'Fatal Python error: Unknown value for option -X'
95+
self.assertEqual(err.splitlines().count(msg), 1)
96+
self.assertEqual(b'', out)
8897

8998
def test_showrefcount(self):
9099
def run_python(*args):

Lib/test/test_embed.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def test_pre_initialization_sys_options(self):
273273
"test_pre_initialization_sys_options", env=env)
274274
expected_output = (
275275
"sys.warnoptions: ['once', 'module', 'default']\n"
276-
"sys._xoptions: {'not_an_option': '1', 'also_not_an_option': '2'}\n"
276+
"sys._xoptions: {'dev': '2', 'utf8': '1'}\n"
277277
"warnings.filters[:3]: ['default', 'module', 'once']\n"
278278
)
279279
self.assertIn(expected_output, out)
@@ -820,15 +820,14 @@ def test_init_from_config(self):
820820
'argv': ['-c', 'arg2'],
821821
'orig_argv': ['python3',
822822
'-W', 'cmdline_warnoption',
823-
'-X', 'cmdline_xoption',
823+
'-X', 'dev',
824824
'-c', 'pass',
825825
'arg2'],
826826
'parse_argv': 2,
827827
'xoptions': [
828-
'config_xoption1=3',
829-
'config_xoption2=',
830-
'config_xoption3',
831-
'cmdline_xoption',
828+
'dev=3',
829+
'utf8',
830+
'dev',
832831
],
833832
'warnoptions': [
834833
'cmdline_warnoption',
@@ -1046,9 +1045,8 @@ def test_init_sys_add(self):
10461045
config = {
10471046
'faulthandler': 1,
10481047
'xoptions': [
1049-
'config_xoption',
1050-
'cmdline_xoption',
1051-
'sysadd_xoption',
1048+
'dev',
1049+
'utf8',
10521050
'faulthandler',
10531051
],
10541052
'warnoptions': [
@@ -1058,9 +1056,12 @@ def test_init_sys_add(self):
10581056
],
10591057
'orig_argv': ['python3',
10601058
'-W', 'ignore:::cmdline_warnoption',
1061-
'-X', 'cmdline_xoption'],
1059+
'-X', 'utf8'],
10621060
}
1063-
self.check_all_configs("test_init_sys_add", config, api=API_PYTHON)
1061+
preconfig = {'utf8_mode': 1}
1062+
self.check_all_configs("test_init_sys_add", config,
1063+
expected_preconfig=preconfig,
1064+
api=API_PYTHON)
10641065

10651066
def test_init_run_main(self):
10661067
code = ('import _testinternalcapi, json; '
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Python now fails to initialize if it finds an invalid :option:`-X` option in the
2+
command line. Patch by Pablo Galindo.

Programs/_testembed.c

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ static int test_pre_initialization_sys_options(void)
211211
* relying on the caller to keep the passed in strings alive.
212212
*/
213213
const wchar_t *static_warnoption = L"once";
214-
const wchar_t *static_xoption = L"also_not_an_option=2";
214+
const wchar_t *static_xoption = L"utf8=1";
215215
size_t warnoption_len = wcslen(static_warnoption);
216216
size_t xoption_len = wcslen(static_xoption);
217217
wchar_t *dynamic_once_warnoption = \
@@ -230,7 +230,7 @@ static int test_pre_initialization_sys_options(void)
230230
PySys_AddWarnOption(L"module");
231231
PySys_AddWarnOption(L"default");
232232
_Py_EMBED_PREINIT_CHECK("Checking PySys_AddXOption\n");
233-
PySys_AddXOption(L"not_an_option=1");
233+
PySys_AddXOption(L"dev=2");
234234
PySys_AddXOption(dynamic_xoption);
235235

236236
/* Delete the dynamic options early */
@@ -548,18 +548,17 @@ static int test_init_from_config(void)
548548
L"-W",
549549
L"cmdline_warnoption",
550550
L"-X",
551-
L"cmdline_xoption",
551+
L"dev",
552552
L"-c",
553553
L"pass",
554554
L"arg2",
555555
};
556556
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
557557
config.parse_argv = 1;
558558

559-
wchar_t* xoptions[3] = {
560-
L"config_xoption1=3",
561-
L"config_xoption2=",
562-
L"config_xoption3",
559+
wchar_t* xoptions[2] = {
560+
L"dev=3",
561+
L"utf8",
563562
};
564563
config_set_wide_string_list(&config, &config.xoptions,
565564
Py_ARRAY_LENGTH(xoptions), xoptions);
@@ -1375,7 +1374,6 @@ static int test_init_read_set(void)
13751374

13761375
static int test_init_sys_add(void)
13771376
{
1378-
PySys_AddXOption(L"sysadd_xoption");
13791377
PySys_AddXOption(L"faulthandler");
13801378
PySys_AddWarnOption(L"ignore:::sysadd_warnoption");
13811379

@@ -1387,14 +1385,14 @@ static int test_init_sys_add(void)
13871385
L"-W",
13881386
L"ignore:::cmdline_warnoption",
13891387
L"-X",
1390-
L"cmdline_xoption",
1388+
L"utf8",
13911389
};
13921390
config_set_argv(&config, Py_ARRAY_LENGTH(argv), argv);
13931391
config.parse_argv = 1;
13941392

13951393
PyStatus status;
13961394
status = PyWideStringList_Append(&config.xoptions,
1397-
L"config_xoption");
1395+
L"dev");
13981396
if (PyStatus_Exception(status)) {
13991397
goto fail;
14001398
}

Python/initconfig.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,49 @@ _PyConfig_InitImportConfig(PyConfig *config)
21292129
return config_init_import(config, 1);
21302130
}
21312131

2132+
// List of known xoptions to validate against the provided ones. Note that all
2133+
// options are listed, even if they are only available if a specific macro is
2134+
// set, like -X showrefcount which requires a debug build. In this case unknown
2135+
// options are silently ignored.
2136+
const wchar_t* known_xoptions[] = {
2137+
L"faulthandler",
2138+
L"showrefcount",
2139+
L"tracemalloc",
2140+
L"importtime",
2141+
L"dev",
2142+
L"utf8",
2143+
L"pycache_prefix",
2144+
L"warn_default_encoding",
2145+
L"no_debug_ranges",
2146+
L"frozen_modules",
2147+
NULL,
2148+
};
2149+
2150+
static const wchar_t*
2151+
_Py_check_xoptions(const PyWideStringList *xoptions, const wchar_t **names)
2152+
{
2153+
for (Py_ssize_t i=0; i < xoptions->length; i++) {
2154+
const wchar_t *option = xoptions->items[i];
2155+
size_t len;
2156+
wchar_t *sep = wcschr(option, L'=');
2157+
if (sep != NULL) {
2158+
len = (sep - option);
2159+
}
2160+
else {
2161+
len = wcslen(option);
2162+
}
2163+
int found = 0;
2164+
for (const wchar_t** name = names; *name != 10000 NULL; name++) {
2165+
if (wcsncmp(option, *name, len) == 0 && (*name)[len] == L'\0') {
2166+
found = 1;
2167+
}
2168+
}
2169+
if (found == 0) {
2170+
return option;
2171+
}
2172+
}
2173+
return NULL;
2174+
}
21322175

21332176
static PyStatus
21342177
config_read(PyConfig *config, int compute_path_config)
@@ -2144,6 +2187,11 @@ config_read(PyConfig *config, int compute_path_config)
21442187
}
21452188

21462189
/* -X options */
2190+
const wchar_t* option = _Py_check_xoptions(&config->xoptions, known_xoptions);
2191+
if (option != NULL) {
2192+
return PyStatus_Error("Unknown value for option -X");
2193+
}
2194+
21472195
if (config_get_xoption(config, L"showrefcount")) {
21482196
config->show_ref_count = 1;
21492197
}

test_foo.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def foo(a=3, *, c, d=2):
2+
pass
3+
foo()

0 commit comments

Comments
 (0)
0