Skip to content

gh-112292 : Catch import error conditions with readline hooks - #112313

Merged
ericsnowcurrently merged 19 commits into
python:mainfrom
tonybaloney:robust_readline_hook
Nov 28, 2023
Merged

gh-112292 : Catch import error conditions with readline hooks#112313
ericsnowcurrently merged 19 commits into
python:mainfrom
tonybaloney:robust_readline_hook

Conversation

@tonybaloney

@tonybaloneytonybaloney commented Nov 22, 2023

Copy link
Copy Markdown
Contributor

Prevents a segmentation fault when modules import (or configure) readline from inside a sub interpreter.

The readlinestate_global macro was error-prone to PyImport_FindModule returning NULL and crashing in about 18 places. I could reproduce 1 easily, but this PR replaces the macro with a function and adds error conditions to the other functions.

Comment threadModules/readline.c Outdated
@ericsnowcurrently

Copy link
Copy Markdown
Member

+1 to fixing the other hooks, whether in this PR or another (though I'd think this PR would be the better place).

@ericsnowcurrentlyericsnowcurrently left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mostly LGTM. I've noted one thing to fix and a couple others to consider.

Comment threadModules/readline.c Outdated
Comment threadModules/readline.c
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
@bedevere-app

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@tonybaloneytonybaloney changed the title gh-112292 : Catch import error conditions with readline on_startup_hookgh-112292 : Catch import error conditions with readline hooksNov 23, 2023
@tonybaloney

Copy link
Copy Markdown
ContributorAuthor

I have made the requested changes; please review again

@ericsnowcurrentlyericsnowcurrently left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the extra effort here. There are a few things we may want to do differently here, for which I've left notes.

Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
Comment threadModules/readline.c Outdated
tonybaloneyand others added 7 commits November 28, 2023 10:04
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
Co-authored-by: Eric Snow <ericsnowcurrently@gmail.com>
@tonybaloney

Copy link
Copy Markdown
ContributorAuthor

@ericsnowcurrently it wasn't possible to check module state inside setup_readline but this code is more robust now so it no longer crashes when imported twice in a sub interpreter. test_builtin and test_readline now correctly fail instead of segfaulting.

$ ./python.exe test_in_interp.py test_builtinRunning test test_builtintest_abs (test.test_builtin.BuiltinTest.test_abs) ... oktest_all (test.test_builtin.BuiltinTest.test_all) ... oktest_any (test.test_builtin.BuiltinTest.test_any) ... oktest_ascii (test.test_builtin.BuiltinTest.test_ascii) ... oktest_bin (test.test_builtin.BuiltinTest.test_bin) ... oktest_bug_27936 (test.test_builtin.BuiltinTest.test_bug_27936) ... oktest_bytearray_extend_error (test.test_builtin.BuiltinTest.test_bytearray_extend_error) ... oktest_bytearray_translate (test.test_builtin.BuiltinTest.test_bytearray_translate) ... oktest_callable (test.test_builtin.BuiltinTest.test_callable) ... oktest_chr (test.test_builtin.BuiltinTest.test_chr) ... oktest_cmp (test.test_builtin.BuiltinTest.test_cmp) ... oktest_compile (test.test_builtin.BuiltinTest.test_compile) ... oktest_compile_ast (test.test_builtin.BuiltinTest.test_compile_ast) ... oktest_compile_async_generator (test.test_builtin.BuiltinTest.test_compile_async_generator)With the PyCF_ALLOW_TOP_LEVEL_AWAIT flag added in 3.8, we want to ... oktest_compile_top_level_await (test.test_builtin.BuiltinTest.test_compile_top_level_await)Test whether code some top level await can be compiled. ... oktest_compile_top_level_await_invalid_cases (test.test_builtin.BuiltinTest.test_compile_top_level_await_invalid_cases) ... oktest_compile_top_level_await_no_coro (test.test_builtin.BuiltinTest.test_compile_top_level_await_no_coro)Make sure top level non-await codes get the correct coroutine flags ... oktest_construct_singletons (test.test_builtin.BuiltinTest.test_construct_singletons) ... oktest_delattr (test.test_builtin.BuiltinTest.test_delattr) ... oktest_dir (test.test_builtin.BuiltinTest.test_dir) ... oktest_divmod (test.test_builtin.BuiltinTest.test_divmod) ... oktest_eval (test.test_builtin.BuiltinTest.test_eval) ... oktest_exec (test.test_builtin.BuiltinTest.test_exec) ... oktest_exec_closure (test.test_builtin.BuiltinTest.test_exec_closure) ... oktest_exec_globals (test.test_builtin.BuiltinTest.test_exec_globals) ... oktest_exec_globals_dict_subclass (test.test_builtin.BuiltinTest.test_exec_globals_dict_subclass) ... oktest_exec_globals_error_on_get (test.test_builtin.BuiltinTest.test_exec_globals_error_on_get) ... oktest_exec_globals_frozen (test.test_builtin.BuiltinTest.test_exec_globals_frozen) ... oktest_exec_redirected (test.test_builtin.BuiltinTest.test_exec_redirected) ... oktest_filter (test.test_builtin.BuiltinTest.test_filter) ... oktest_filter_dealloc (test.test_builtin.BuiltinTest.test_filter_dealloc) ... skipped "resource 'cpu' is not enabled"test_filter_pickle (test.test_builtin.BuiltinTest.test_filter_pickle) ... oktest_format (test.test_builtin.BuiltinTest.test_format) ... oktest_general_eval (test.test_builtin.BuiltinTest.test_general_eval) ... oktest_getattr (test.test_builtin.BuiltinTest.test_getattr) ... oktest_hasattr (test.test_builtin.BuiltinTest.test_hasattr) ... oktest_hash (test.test_builtin.BuiltinTest.test_hash) ... oktest_hex (test.test_builtin.BuiltinTest.test_hex) ... oktest_id (test.test_builtin.BuiltinTest.test_id) ... oktest_import (test.test_builtin.BuiltinTest.test_import) ... oktest_input (test.test_builtin.BuiltinTest.test_input) ... oktest_isinstance (test.test_builtin.BuiltinTest.test_isinstance) ... oktest_issubclass (test.test_builtin.BuiltinTest.test_issubclass) ... oktest_iter (test.test_builtin.BuiltinTest.test_iter) ... oktest_len (test.test_builtin.BuiltinTest.test_len) ... oktest_map (test.test_builtin.BuiltinTest.test_map) ... oktest_map_pickle (test.test_builtin.BuiltinTest.test_map_pickle) ... oktest_max (test.test_builtin.BuiltinTest.test_max) ... oktest_min (test.test_builtin.BuiltinTest.test_min) ... oktest_neg (test.test_builtin.BuiltinTest.test_neg) ... oktest_next (test.test_builtin.BuiltinTest.test_next) ... oktest_oct (test.test_builtin.BuiltinTest.test_oct) ... oktest_open (test.test_builtin.BuiltinTest.test_open) ... oktest_open_default_encoding (test.test_builtin.BuiltinTest.test_open_default_encoding) ... oktest_open_non_inheritable (test.test_builtin.BuiltinTest.test_open_non_inheritable) ... oktest_ord (test.test_builtin.BuiltinTest.test_ord) ... oktest_pow (test.test_builtin.BuiltinTest.test_pow) ... oktest_repr (test.test_builtin.BuiltinTest.test_repr) ... oktest_round (test.test_builtin.BuiltinTest.test_round) ... oktest_round_large (test.test_builtin.BuiltinTest.test_round_large) ... oktest_setattr (test.test_builtin.BuiltinTest.test_setattr) ... oktest_sum (test.test_builtin.BuiltinTest.test_sum) ... oktest_sum_accuracy (test.test_builtin.BuiltinTest.test_sum_accuracy) ... oktest_type (test.test_builtin.BuiltinTest.test_type) ... oktest_vars (test.test_builtin.BuiltinTest.test_vars) ... oktest_warning_notimplemented (test.test_builtin.BuiltinTest.test_warning_notimplemented) ... oktest_zip (test.test_builtin.BuiltinTest.test_zip) ... oktest_zip_bad_iterable (test.test_builtin.BuiltinTest.test_zip_bad_iterable) ... oktest_zip_pickle (test.test_builtin.BuiltinTest.test_zip_pickle) ... oktest_zip_pickle_strict (test.test_builtin.BuiltinTest.test_zip_pickle_strict) ... oktest_zip_pickle_strict_fail (test.test_builtin.BuiltinTest.test_zip_pickle_strict_fail) ... oktest_zip_result_gc (test.test_builtin.BuiltinTest.test_zip_result_gc) ... oktest_zip_strict (test.test_builtin.BuiltinTest.test_zip_strict) ... oktest_zip_strict_error_handling (test.test_builtin.BuiltinTest.test_zip_strict_error_handling) ... oktest_zip_strict_error_handling_stopiteration (test.test_builtin.BuiltinTest.test_zip_strict_error_handling_stopiteration) ... oktest_zip_strict_iterators (test.test_builtin.BuiltinTest.test_zip_strict_iterators) ... oktest_immortals (test.test_builtin.ImmortalTests.test_immortals) ... oktest_list_repeat_respect_immortality (test.test_builtin.ImmortalTests.test_list_repeat_respect_immortality) ... oktest_tuple_repeat_respect_immortality (test.test_builtin.ImmortalTests.test_tuple_repeat_respect_immortality) ... oktest_input_no_stdout_fileno (test.test_builtin.PtyTests.test_input_no_stdout_fileno) ... ERRORtest_input_tty (test.test_builtin.PtyTests.test_input_tty) ... ERRORtest_input_tty_non_ascii (test.test_builtin.PtyTests.test_input_tty_non_ascii) ... ERRORtest_input_tty_non_ascii_unicode_errors (test.test_builtin.PtyTests.test_input_tty_non_ascii_unicode_errors) ... ERRORtest_cleanup (test.test_builtin.ShutdownTest.test_cleanup) ... oktest_breakpoint (test.test_builtin.TestBreakpoint.test_breakpoint) ... oktest_breakpoint_with_args_and_keywords (test.test_builtin.TestBreakpoint.test_breakpoint_with_args_and_keywords) ... oktest_breakpoint_with_breakpointhook_reset (test.test_builtin.TestBreakpoint.test_breakpoint_with_breakpointhook_reset) ... oktest_breakpoint_with_breakpointhook_set (test.test_builtin.TestBreakpoint.test_breakpoint_with_breakpointhook_set) ... oktest_breakpoint_with_passthru_error (test.test_builtin.TestBreakpoint.test_breakpoint_with_passthru_error) ... oktest_envar_good_path_builtin (test.test_builtin.TestBreakpoint.test_envar_good_path_builtin) ... oktest_envar_good_path_empty_string (test.test_builtin.TestBreakpoint.test_envar_good_path_empty_string) ... oktest_envar_good_path_noop_0 (test.test_builtin.TestBreakpoint.test_envar_good_path_noop_0) ... oktest_envar_good_path_other (test.test_builtin.TestBreakpoint.test_envar_good_path_other) ... oktest_envar_ignored_when_hook_is_set (test.test_builtin.TestBreakpoint.test_envar_ignored_when_hook_is_set) ... oktest_envar_unimportable (test.test_builtin.TestBreakpoint.test_envar_unimportable) ... oktest_runtime_error_when_hook_is_lost (test.test_builtin.TestBreakpoint.test_runtime_error_when_hook_is_lost) ... oktest_bad_arguments (test.test_builtin.TestSorted.test_bad_arguments) ... oktest_baddecorator (test.test_builtin.TestSorted.test_baddecorator) ... oktest_basic (test.test_builtin.TestSorted.test_basic) ... oktest_inputtypes (test.test_builtin.TestSorted.test_inputtypes) ... oktest_bad_args (test.test_builtin.TestType.test_bad_args) ... oktest_bad_slots (test.test_builtin.TestType.test_bad_slots) ... oktest_namespace_order (test.test_builtin.TestType.test_namespace_order) ... oktest_new_type (test.test_builtin.TestType.test_new_type) ... oktest_type_doc (test.test_builtin.TestType.test_type_doc) ... oktest_type_name (test.test_builtin.TestType.test_type_name) ... oktest_type_nokwargs (test.test_builtin.TestType.test_type_nokwargs) ... oktest_type_qualname (test.test_builtin.TestType.test_type_qualname) ... oktest_type_typeparams (test.test_builtin.TestType.test_type_typeparams) ... okbin (builtins)Doctest: builtins.bin ... okhex (builtins.bytearray)Doctest: builtins.bytearray.hex ... okhex (builtins.bytes)Doctest: builtins.bytes.hex ... okas_integer_ratio (builtins.float)Doctest: builtins.float.as_integer_ratio ... okfromhex (builtins.float)Doctest: builtins.float.fromhex ... okhex (builtins.float)Doctest: builtins.float.hex ... okhex (builtins)Doctest: builtins.hex ... okint (builtins)Doctest: builtins.int ... okas_integer_ratio (builtins.int)Doctest: builtins.int.as_integer_ratio ... okbit_count (builtins.int)Doctest: builtins.int.bit_count ... okbit_length (builtins.int)Doctest: builtins.int.bit_length ... okhex (builtins.memoryview)Doctest: builtins.memoryview.hex ... okoct (builtins)Doctest: builtins.oct ... okzip (builtins)Doctest: builtins.zip ... ok======================================================================ERROR: test_input_no_stdout_fileno (test.test_builtin.PtyTests.test_input_no_stdout_fileno)----------------------------------------------------------------------Traceback (most recent call last): File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2314, in test_input_no_stdout_fileno lines = self.run_child(child, b"quux\r") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2187, in run_child old_sighup = signal.signal(signal.SIGHUP, self.handle_sighup) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/signal.py", line 56, in signal handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ValueError: signal only works in main thread of the main interpreter======================================================================ERROR: test_input_tty (test.test_builtin.PtyTests.test_input_tty)----------------------------------------------------------------------Traceback (most recent call last): File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2284, in test_input_tty self.check_input_tty("prompt", b"quux") File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2269, in check_input_tty lines = self.run_child(child, terminal_input + b"\r\n") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2187, in run_child old_sighup = signal.signal(signal.SIGHUP, self.handle_sighup) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/signal.py", line 56, in signal handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ValueError: signal only works in main thread of the main interpreter======================================================================ERROR: test_input_tty_non_ascii (test.test_builtin.PtyTests.test_input_tty_non_ascii)----------------------------------------------------------------------Traceback (most recent call last): File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2299, in test_input_tty_non_ascii self.check_input_tty("prompté", b"quux\xe9", "utf-8") File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2269, in check_input_tty lines = self.run_child(child, terminal_input + b"\r\n") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2187, in run_child old_sighup = signal.signal(signal.SIGHUP, self.handle_sighup) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/signal.py", line 56, in signal handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ValueError: signal only works in main thread of the main interpreter======================================================================ERROR: test_input_tty_non_ascii_unicode_errors (test.test_builtin.PtyTests.test_input_tty_non_ascii_unicode_errors)----------------------------------------------------------------------Traceback (most recent call last): File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2304, in test_input_tty_non_ascii_unicode_errors self.check_input_tty("prompté", b"quux\xe9", "ascii") File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2269, in check_input_tty lines = self.run_child(child, terminal_input + b"\r\n") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/test/test_builtin.py", line 2187, in run_child old_sighup = signal.signal(signal.SIGHUP, self.handle_sighup) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/anthonyshaw/projects/cpython/Lib/signal.py", line 56, in signal handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ValueError: signal only works in main thread of the main interpreter----------------------------------------------------------------------Ran 122 tests in 0.298sFAILED (errors=4, skipped=1)test test_builtin failed
$ ./python.exe test_in_interp.py test_readlineRunning test test_readlinetest_readline skipped -- module readline does not support loading in subinterpreters

@ericsnowcurrently

Copy link
Copy Markdown
Member

it wasn't possible to check module state inside setup_readline

I'm guessing it's because PyState_FindModule() will always return NULL until after the module init func has already finished. That's fine.

but this code is more robust now so it no longer crashes when imported twice in a sub interpreter.

Yeah, that's the higher priority thing. The module is definitely in a better place now.

@ericsnowcurrentlyericsnowcurrently left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@ericsnowcurrently

Copy link
Copy Markdown
Member

Thanks for taking care of this, @tonybaloney!

@ericsnowcurrently

Copy link
Copy Markdown
Member

How much of this should be backported to 3.12?

@tonybaloney

Copy link
Copy Markdown
ContributorAuthor

How much of this should be backported to 3.12?

All of it could be

@tonybaloney
tonybaloney deleted the robust_readline_hook branch November 28, 2023 02:01
@ericsnowcurrently

Copy link
Copy Markdown
Member

@Yhg1s, what do you think about backporting this as-is?

aisk pushed a commit to aisk/cpython that referenced this pull request Feb 11, 2024
…ythongh-112313)
Prevents a segmentation fault in registered hooks for the readline library, but only when the readline module is loaded inside an isolated sub interpreter. The module is single-phase init so loading it fails, but not until the module init function has already run, where the readline hooks get registered.
The readlinestate_global macro was error-prone to PyImport_FindModule returning NULL and crashing in about 18 places. I could reproduce 1 easily, but this PR replaces the macro with a function and adds error conditions to the other functions.
Glyphack pushed a commit to Glyphack/cpython that referenced this pull request Sep 2, 2024
…ythongh-112313)
Prevents a segmentation fault in registered hooks for the readline library, but only when the readline module is loaded inside an isolated sub interpreter. The module is single-phase init so loading it fails, but not until the module init function has already run, where the readline hooks get registered.
The readlinestate_global macro was error-prone to PyImport_FindModule returning NULL and crashing in about 18 places. I could reproduce 1 easily, but this PR replaces the macro with a function and adds error conditions to the other functions.
Sign up for freeto 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.

3 participants

@tonybaloney@ericsnowcurrently@corona10