Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Oct 13, 2021

Conversation

pablogsal
Copy link
Member

@pablogsal pablogsal commented Oct 8, 2021

@pablogsal
Copy link
Member Author

Hey @vstinner, would you be ok merging some refined version of this draft? We found that using -X ---- can be very confusing and annoying when you make typos, so I think having errors if you pass some unknown stuff will be oughweighting any downside of making this more strict.

@@ -2121,6 +2121,19 @@ _PyConfig_InitImportConfig(PyConfig *config)
return config_init_import(config, 1);
}

const wchar_t* known_xoptions[] = {
L"faulthandler",
L"showrefcount",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should Python ignore silently this option if Python is not build with Py_REF_DEBUG? Or should we add #ifdef Py_REF_DEBUG? Using PYTHONDUMPREFS=1 env var doesn't fail with an error even if Python is not build with Py_TRACE_REFS.

IMO the least surprising behavior is to ignore silently the option. So leave the code as it is.

Python/preconfig.c Outdated Show resolved Hide resolved
@@ -2136,6 +2149,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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe document in Doc/using/cmdline.rst that Python now fails if it gets an unknown -X option to help detecting typos.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there is any way using the PyStatus_Error to provide a custom string? I would like to include the failed option but seems that nothing cleans the string provided here so I am not sure if is possible to customize the erro with the actual option :(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there is any way using the PyStatus_Error to provide a custom string?

Currently, it's not possible. You should extend the API for that. You should add a static buffer just for that somewhere, or allocate a string, and decide when and how it's released. For PEP 587, I decided to keep it simple (KISS) and only use constant string.

@pablogsal
Copy link
Member Author

@vstinner Done! I have addressed your feedback and I have updated some tests that were failing.

@@ -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],
Copy link
Member Author

@pablogsal pablogsal Oct 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bonus! This was a bug because the Xoption was provided as utf8 (with space) and not utf8 so it was silently ignored :(

@vstinner @zooba

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, or you can write "-X", "utf8". I suppose that both work.

@pablogsal pablogsal changed the title Fail if an invalid X-option is provided in the command line bpo-45445: Fail if an invalid X-option is provided in the command line Oct 12, 2021
@@ -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],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, or you can write "-X", "utf8". I suppose that both work.

pass

with self.assertRaises(TypeError):
foo(w=40000,z=50000,o=60000)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Secrets 👀

@@ -2121,6 +2121,45 @@ _PyConfig_InitImportConfig(PyConfig *config)
return config_init_import(config, 1);
}

const wchar_t* known_xoptions[] = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind to add a comment saying 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 (Py_REF_DEBUG). Say that unknown options are silently ignored in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@@ -2136,6 +2149,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");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if there is any way using the PyStatus_Error to provide a custom string?

Currently, it's not possible. You should extend the API for that. You should add a static buffer just for that somewhere, or allocate a string, and decide when and how it's released. For PEP 587, I decided to keep it simple (KISS) and only use constant string.

@@ -0,0 +1,2 @@
Python now fails to initialize if an invalid :option:`-X` option in the
command line. Patch by Pablo Galindo.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if... what? :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for the fix :-)

Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. There is just a typo in the NEWS entry.

@pablogsal
Copy link
Member Author

@vstinner I have added a small static buffer for now in b6fe156 to allow customizing the message. I will prepare a better generic API in a different PR in the future.

Python/initconfig.c Outdated Show resolved Hide resolved
Copy link
Member

@vstinner vstinner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Copy link
Contributor

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pablogsal Thanks 👍 Looks great ⭐

Should we update an example in sys._xoptions docs? It crashes with this patch. We could use e.g. -Xutf8 -Xpycache_prefix=pycachepath.

@@ -83,8 +83,17 @@ def get_xoptions(*args):
opts = get_xoptions()
self.assertEqual(opts, {})

opts = get_xoptions('-Xa', '-Xb=c,d=e')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have tests do X-options with values? Maybe we can use pycache_prefix to keep this covered.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_embed has a bunch, but I can add one here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected in the last commit! Thanks for the suggestion @felixxm

Copy link
Contributor

@felixxm felixxm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updates 👍

@pablogsal pablogsal merged commit db2b6a2 into python:main Oct 13, 2021
@pablogsal pablogsal deleted the unknown branch October 13, 2021 17:08
@tirkarthi
Copy link
Member

@pablogsal The PR has added test_foo.py in the repo's toplevel directory. Is it used somewhere in the tests where it's specifically needs to be in toplevel directory rather than under tests folders?

@pablogsal
Copy link
Member Author

@pablogsal The PR has added test_foo.py in the repo's toplevel directory. Is it used somewhere in the tests where it's specifically needs to be in toplevel directory rather than under tests folders?

🤦‍♂️No, that was a mistake that slip us. I will clean it up. Apologies

@pablogsal
Copy link
Member Author

🤦‍♂️No, that was a mistake that slip us. I will clean it up. Apologies

Fixed in #28972

@tirkarthi
Copy link
Member

No worries, thanks @pablogsal :)

@kovidgoyal
Copy link

Cross posting:
Your documentation has stated for YEARS that arbitrary values can be passed via PyConfig.xoptions or -X. 9583cac

Quoting your own documentation:
"It also allows passing arbitrary values and retrieving them through the sys._xoptions dictionary."

There is client software out there relying on this. kovidgoyal/kitty#5223

Please do not randomnly break backwards compatibility, without even a deprecation period, because something "annoys" you.

kovidgoyal added a commit to kovidgoyal/kitty that referenced this pull request Jun 23, 2022
Apparently in Python-land its acceptable behavior to break backward
compatibility with documented interfaces on a whim. Bloody joke.
python/cpython#28823

Fixes #5223
@zooba
Copy link
Member

zooba commented Jun 23, 2022

The documentation still says that arbitrary values may be passed: https://docs.python.org/3.11/using/cmdline.html#cmdoption-X

The -X command line argument is clearly public API and shouldn't change like this without a deprecation period. (I suspect in this case, adding a "this was possibly misspelled" warning is likely a better/less-hostile option than changing the behaviour anyway.)

sys._xoptions is in the weird place of being underscored and documented, and explicitly documented as being CPython specific, which would explain the leading underscore. But the definition of _xoptions isn't changing in this case - it's the change to -X making a previously documented and useful feature into an error, which requires deprecation.

@gvanrossum
Copy link
Member

I've never had a use for sys._xoptions, but I've mistyped (or mis-guessed) -X flag names many times, so at least a warning about a misspelling would be appreciated.

I feel the python command line is not a public API in the same way that we talk about APIs for Python code. And doubly so for the -X flag which is (usually) used to control interpreter internals.

Before we roll this back I would like to understand what problem Kitty is solving by passing its own -X flags (the Kitty issue doesn't say, it was just immediately closed as "bug in CPython"). Maybe we can help the Kitty devs find a better solution for their problem?

@arhadthedev
Copy link
Member

arhadthedev commented Jun 23, 2022

I feel the python command line is not a public API in the same way that we talk about APIs for Python code

I would say that CLI is an API for shell scripts. As a result, we need to account that eventhough scripts that pass nonexistent -W worked incorrectly, they worked without abruption or serious logical bug, and probably as a brick of some more complex production system.

@gvanrossum
Copy link
Member

If a script was passing an incorrect -X option that was interpreted as a no-op, the new error just found a bug in the script -- presumably the script intended some other subtle effect that was never properly tested for.

@pablogsal
Copy link
Member Author

Let's redirect the discussion to the issue (#89608) so we can discuss in a single place

@kovidgoyal
Copy link

kovidgoyal commented Jun 24, 2022 via email

pablogsal added a commit to pablogsal/cpython that referenced this pull request Jul 11, 2022
pablogsal added a commit that referenced this pull request Jul 31, 2022
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jul 31, 2022
… in the command line (pythonGH-28823)" (pythonGH-94745)

(cherry picked from commit aa37ffd)

Co-authored-by: Pablo Galindo Salgado <[email protected]>
miss-islington added a commit that referenced this pull request Jul 31, 2022
… in the command line (GH-28823)" (GH-94745)

(cherry picked from commit aa37ffd)

Co-authored-by: Pablo Galindo Salgado <[email protected]>
@bedevere-bot
Copy link

⚠️⚠️⚠️ Buildbot failure ⚠️⚠️⚠️

Hi! The buildbot s390x RHEL7 LTO 3.11 has failed when building commit 147a9a8.

What do you need to do:

  1. Don't panic.
  2. Check the buildbot page in the devguide if you don't know what the buildbots are or how they work.
  3. Go to the page of the buildbot that failed (https://buildbot.python.org/all/#builders/924/builds/291) and take a look at the build logs.
  4. Check if the failure is related to this commit (147a9a8) or if it is a false positive.
  5. If the failure is related to this commit, please, reflect that on the issue and make a new Pull Request with a fix.

You can take a look at the buildbot page here:

https://buildbot.python.org/all/#builders/924/builds/291

Summary of the results of the build (if available):

== Tests result: ENV CHANGED ==

415 tests OK.

10 slowest tests:

  • test_concurrent_futures: 2 min 45 sec
  • test_tools: 2 min 24 sec
  • test_multiprocessing_spawn: 2 min 22 sec
  • test_multiprocessing_forkserver: 1 min 24 sec
  • test_tokenize: 1 min 23 sec
  • test_multiprocessing_fork: 1 min 21 sec
  • test_asyncio: 1 min 15 sec
  • test_lib2to3: 1 min 2 sec
  • test_unparse: 59.4 sec
  • test_signal: 48.7 sec

1 test altered the execution environment:
test_asyncio

20 tests skipped:
test_devpoll test_gdb test_idle test_ioctl test_kqueue
test_launcher test_msilib test_smtpnet test_ssl test_startfile
test_tcl test_tix test_tk test_ttk_guionly test_ttk_textonly
test_turtle test_winconsoleio test_winreg test_winsound
test_zipfile64

Total duration: 6 min 37 sec

Click to see traceback logs
Traceback (most recent call last):
  File "/home/dje/cpython-buildarea/3.11.edelsohn-rhel-z.lto/build/Lib/multiprocessing/resource_tracker.py", line 209, in main
    cache[rtype].remove(name)
KeyError: '/psm_5e6812aa'


Traceback (most recent call last):
  File "/home/dje/cpython-buildarea/3.11.edelsohn-rhel-z.lto/build/Lib/multiprocessing/resource_tracker.py", line 209, in main
    cache[rtype].remove(name)
KeyError: '/psm_ce63b861'


Traceback (most recent call last):
  File "/home/dje/cpython-buildarea/3.11.edelsohn-rhel-z.lto/build/Lib/multiprocessing/resource_tracker.py", line 209, in main
    cache[rtype].remove(name)
KeyError: '/psm_67b34c9e'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants