From d0ea61cb87ea3a3d40ef6f90a5ff8dea75fa6f3a Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 11 May 2026 16:56:09 +0900 Subject: [PATCH 1/2] scheduler: rewind CFP after EC_PUSH_TAG on non-local jumps (#16916) Both rb_fiber_scheduler_unblock and rb_fiber_scheduler_fiber_interrupt used EC_PUSH_TAG but did not capture ec->cfp beforehand nor call rb_vm_rewind_cfp in the non-TAG_NONE branch. Without this, stale call frames can remain on the stack after an exception or other non-local jump, matching the structure rb_protect uses for the same pattern. --- scheduler.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scheduler.c b/scheduler.c index 3205bb3bc9a7c3..7efd4274cb59a5 100644 --- a/scheduler.c +++ b/scheduler.c @@ -680,10 +680,14 @@ rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber) int saved_interrupt_mask = ec->interrupt_mask; ec->interrupt_mask |= PENDING_INTERRUPT_MASK; + rb_control_frame_t *volatile cfp = ec->cfp; EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { result = rb_funcall(scheduler, id_unblock, 2, blocker, fiber); } + else { + rb_vm_rewind_cfp(ec, cfp); + } EC_POP_TAG(); ec->interrupt_mask = saved_interrupt_mask; @@ -1145,10 +1149,14 @@ VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exc int saved_interrupt_mask = ec->interrupt_mask; ec->interrupt_mask |= PENDING_INTERRUPT_MASK; + rb_control_frame_t *volatile cfp = ec->cfp; EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { result = rb_check_funcall(scheduler, id_fiber_interrupt, 2, arguments); } + else { + rb_vm_rewind_cfp(ec, cfp); + } EC_POP_TAG(); ec->interrupt_mask = saved_interrupt_mask; From d7ef97204d907561eb67828382f7bb17397313c6 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Mon, 11 May 2026 13:25:29 +0300 Subject: [PATCH 2/2] [Bug #22063] Reject NaN Regexp timeout values --- re.c | 2 +- test/ruby/test_regexp.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/re.c b/re.c index bb6af74eb57f36..317790fba80e5e 100644 --- a/re.c +++ b/re.c @@ -3971,7 +3971,7 @@ static void set_timeout(rb_hrtime_t *hrt, VALUE timeout) { double timeout_d = NIL_P(timeout) ? 0.0 : NUM2DBL(timeout); - if (!NIL_P(timeout) && timeout_d <= 0) { + if (!NIL_P(timeout) && !(timeout_d > 0)) { rb_raise(rb_eArgError, "invalid timeout: %"PRIsVALUE, timeout); } double2hrtime(hrt, timeout_d); diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index d545d983dc38b1..69e15bd4df687d 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -2035,6 +2035,7 @@ def test_s_timeout_corner_cases Regexp.timeout = 1e300 assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + assert_raise(ArgumentError) { Regexp.timeout = Float::NAN } assert_raise(ArgumentError) { Regexp.timeout = 0 } assert_raise(ArgumentError) { Regexp.timeout = -1 } @@ -2127,6 +2128,7 @@ def test_timeout_corner_cases assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout) + assert_raise(ArgumentError) { Regexp.new("foo", timeout: Float::NAN) } assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) } assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) } end;