From bf920ef6af65425e39047cb2b14165bebab86651 Mon Sep 17 00:00:00 2001 From: Sorah Fukumori Date: Thu, 21 May 2026 16:19:38 +0900 Subject: [PATCH 1/3] test_thread_join_hang: join leaked inner sleeper (#17068) SleepingUnblockScheduler#unblock deliberately breaks the join path, so the inner Thread.new{sleep(0.01)} created inside Fiber.schedule is abandoned mid-sleep when the outer thread exits. It eventually wakes and lingers in Thread.list, breaking cleanup_threads in later test files (e.g. test/-ext-/thread/test_instrumentation_api.rb). Capture the inner thread and join it in an ensure. 18) Failure: TestThreadInstrumentation#test_thread_pass_multi_thread [/build/ruby4.0/test/-ext-/thread/test_instrumentation_api.rb:289]: <[#]> expected but was <[#, #]>. --- test/fiber/test_thread.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/fiber/test_thread.rb b/test/fiber/test_thread.rb index 0247f330d94403..4d2fbde9ed20a8 100644 --- a/test/fiber/test_thread.rb +++ b/test/fiber/test_thread.rb @@ -156,16 +156,20 @@ def test_broken_unblock end def test_thread_join_hang + inner = nil thread = Thread.new do scheduler = SleepingUnblockScheduler.new Fiber.set_scheduler scheduler Fiber.schedule do - Thread.new{sleep(0.01)}.value + inner = Thread.new{sleep(0.01)} + inner.value end end thread.join + ensure + inner&.join end end From d1391a000359655f948e16d7f2b0e85890082b5a Mon Sep 17 00:00:00 2001 From: Sampo Kuokkanen Date: Thu, 21 May 2026 17:14:30 +0900 Subject: [PATCH 2/3] Fix defined? for protected methods defined in a module me->defined_class is 0 for methods stored on a module, so the protected visibility check in vm_defined always failed and defined? returned nil even when the call would succeed. Use rb_callable_method_entry_with_refinements (where defined_class is populated) and the same vm_defined_class_for_protected_call helper as the call path. defined? now agrees with whether the method could actually be called. [Bug #22076] --- test/ruby/test_defined.rb | 28 ++++++++++++++++++++++++++++ vm_insnhelper.c | 10 +++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/test/ruby/test_defined.rb b/test/ruby/test_defined.rb index db1fdc8e25a3c5..75ed1a7534dc42 100644 --- a/test/ruby/test_defined.rb +++ b/test/ruby/test_defined.rb @@ -62,6 +62,34 @@ def test_defined_protected_method f.bar(Class.new(Foo).new) { |v| assert(v, "inherited protected method") } end + module ProtectedInModule + def m + :m + end + protected :m + def call_m(o) + o.m + end + def defined_m(o) + defined?(o.m) + end + end + class ProtectedIncluderA + include ProtectedInModule + end + class ProtectedIncluderB + include ProtectedInModule + end + + def test_defined_protected_method_in_included_module + a = ProtectedIncluderA.new + b = ProtectedIncluderB.new + assert_equal(:m, a.call_m(a)) + assert_equal(:m, a.call_m(b)) + assert_equal("method", a.defined_m(a)) + assert_equal("method", a.defined_m(b)) + end + def test_defined_undefined_method f = Foo.new assert_nil(defined?(f.quux)) # undefined method diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 783246a7efc513..75d5023566e7c6 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -5485,21 +5485,21 @@ vm_defined(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, rb_num_t op_ break; case DEFINED_METHOD:{ VALUE klass = CLASS_OF(v); - const rb_method_entry_t *me = rb_method_entry_with_refinements(klass, SYM2ID(obj), NULL); + const rb_callable_method_entry_t *cme = rb_callable_method_entry_with_refinements(klass, SYM2ID(obj), NULL); - if (me) { - switch (METHOD_ENTRY_VISI(me)) { + if (cme) { + switch (METHOD_ENTRY_VISI(cme)) { case METHOD_VISI_PRIVATE: break; case METHOD_VISI_PROTECTED: - if (!rb_obj_is_kind_of(GET_SELF(), rb_class_real(me->defined_class))) { + if (!rb_obj_is_kind_of(GET_SELF(), vm_defined_class_for_protected_call(cme))) { break; } case METHOD_VISI_PUBLIC: return true; break; default: - rb_bug("vm_defined: unreachable: %u", (unsigned int)METHOD_ENTRY_VISI(me)); + rb_bug("vm_defined: unreachable: %u", (unsigned int)METHOD_ENTRY_VISI(cme)); } } else { From 81c68150a39cb24ef321bd7fee2b14bb80a9db00 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 21 May 2026 20:25:48 +0900 Subject: [PATCH 3/3] [ruby/strscan] Fix call-seq return values (https://github.com/ruby/strscan/pull/207) Some `call-seq` comments did not match the actual return values (documentation only, no behavior change): * `match?` returns the match size, not a position * `skip` was missing the `->` arrow * `scan_full` can also return a length * `scan_byte` can return `nil` at end of string https://github.com/ruby/strscan/commit/db4cc96c2e --- ext/strscan/strscan.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/strscan/strscan.c b/ext/strscan/strscan.c index 7e21a21f7eb2d8..936fb0f81baad7 100644 --- a/ext/strscan/strscan.c +++ b/ext/strscan/strscan.c @@ -822,7 +822,7 @@ strscan_scan(VALUE self, VALUE re) * :include: strscan/link_refs.txt * * call-seq: - * match?(pattern) -> updated_position or nil + * match?(pattern) -> match_size or nil * * Attempts to [match][17] the given `pattern` * at the beginning of the [target substring][3]; @@ -882,7 +882,7 @@ strscan_match_p(VALUE self, VALUE re) /* * :markup: markdown * call-seq: - * skip(pattern) match_size or nil + * skip(pattern) -> match_size or nil * * :include: strscan/link_refs.txt * :include: strscan/methods/skip.md @@ -956,7 +956,7 @@ strscan_check(VALUE self, VALUE re) /* * call-seq: - * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or nil + * scan_full(pattern, advance_pointer_p, return_string_p) -> matched_substring or length or nil * * Equivalent to one of the following: * @@ -1202,7 +1202,7 @@ strscan_getch(VALUE self) /* * call-seq: - * scan_byte -> integer_byte + * scan_byte -> integer_byte or nil * * Scans one byte and returns it as an integer. * This method is not multibyte character sensitive.