diff options
| author | John Hawthorn <john@hawthorn.email> | 2026-02-07 08:44:08 -0800 |
|---|---|---|
| committer | Takashi Kokubun <takashikkbn@gmail.com> | 2026-02-09 16:38:18 -0500 |
| commit | c6d9ba58c50fd9c07023453d71cb55b4b9c36957 (patch) | |
| tree | a105c997f80dd4e47050ff384e799c65e221da42 | |
| parent | 584c3b6465ec40226687e9699d2b652d9ad31a8f (diff) | |
Fix signal crash during keyword argument call
64f508ade8 changed rb_threadptr_raise to call rb_exception_setup,
which uses rb_scan_args with RB_SCAN_ARGS_PASS_CALLED_KEYWORDS.
This checked rb_keyword_given_p(), which read the interrupted
frame's keyword state rather than the signal raise arguments,
causing a crash when a signal arrived during a keyword call.
Revert rb_threadptr_raise to use rb_make_exception directly, and
have thread_raise_m call rb_exception_setup where
rb_keyword_given_p() reflects the correct frame.
[Bug #21865]
| -rw-r--r-- | test/ruby/test_signal.rb | 14 | ||||
| -rw-r--r-- | thread.c | 21 |
2 files changed, 32 insertions, 3 deletions
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb index a2bdf02b88..b8eb825468 100644 --- a/test/ruby/test_signal.rb +++ b/test/ruby/test_signal.rb @@ -350,4 +350,18 @@ class TestSignal < Test::Unit::TestCase loop { sleep } End end if Process.respond_to?(:kill) && Process.respond_to?(:daemon) + + def test_signal_during_kwarg_call + status = assert_in_out_err([], <<~'RUBY', [], [], success: false) + Thread.new do + sleep 0.1 + Process.kill("TERM", $$) + end + + loop do + File.open(IO::NULL, kwarg: true) {} + end + RUBY + assert_predicate(status, :signaled?) if Signal.list.include?("QUIT") + end if Process.respond_to?(:kill) end @@ -2737,11 +2737,18 @@ rb_threadptr_ready(rb_thread_t *th) static VALUE rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv) { + VALUE exc; + if (rb_threadptr_dead(target_th)) { return Qnil; } - VALUE exception = rb_exception_setup(argc, argv); + if (argc == 0) { + exc = rb_exc_new(rb_eRuntimeError, 0, 0); + } + else { + exc = rb_make_exception(argc, argv); + } /* making an exception object can switch thread, so we need to check thread deadness again */ @@ -2749,7 +2756,8 @@ rb_threadptr_raise(rb_thread_t *target_th, int argc, VALUE *argv) return Qnil; } - rb_threadptr_pending_interrupt_enque(target_th, exception); + rb_ec_setup_exception(GET_EC(), exc, Qundef); + rb_threadptr_pending_interrupt_enque(target_th, exc); rb_threadptr_interrupt(target_th); return Qnil; @@ -2933,7 +2941,14 @@ thread_raise_m(int argc, VALUE *argv, VALUE self) const rb_thread_t *current_th = GET_THREAD(); threadptr_check_pending_interrupt_queue(target_th); - rb_threadptr_raise(target_th, argc, argv); + + if (rb_threadptr_dead(target_th)) { + return Qnil; + } + + VALUE exception = rb_exception_setup(argc, argv); + rb_threadptr_pending_interrupt_enque(target_th, exception); + rb_threadptr_interrupt(target_th); /* To perform Thread.current.raise as Kernel.raise */ if (current_th == target_th) { |
