summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Hawthorn <john@hawthorn.email>2026-02-07 08:44:08 -0800
committerJohn Hawthorn <john@hawthorn.email>2026-02-07 22:15:37 -0800
commit3e02e99e425ee1c2e4a7cee625eaea6ed1faf1c4 (patch)
treea2e085f39e494a5f97ac98821d228faae86300a8
parentbdb3b8640644aa11772e9f76307e4d35b1500f39 (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.rb14
-rw-r--r--thread.c21
2 files changed, 32 insertions, 3 deletions
diff --git a/test/ruby/test_signal.rb b/test/ruby/test_signal.rb
index 091a66d5da..1ee3720ded 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
diff --git a/thread.c b/thread.c
index bdca1e2469..99252168de 100644
--- a/thread.c
+++ b/thread.c
@@ -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) {