From 8267e508c56ae9d587e55efc23a37fa723a33d89 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 15 May 2026 18:43:00 -0400 Subject: [PATCH 1/6] ZJIT: x64: Prefer 7-byte sign extending `mov` over 10-byte `movabs` Relevant for small negative immediates. Previously: # Insn: v16 SetLocal l1, EP@3, v10 mov rsi, qword ptr [r13 + 0x20] mov rsi, qword ptr [rsi - 8] and rsi, 0xfffffffffffffffc # call rb_vm_env_write push rdi push rdi mov rdx, rdi mov rdi, rsi movabs rsi, 0xfffffffffffffffd --- zjit/src/asm/x86_64/mod.rs | 89 +++++++++++++++--------------------- zjit/src/asm/x86_64/tests.rs | 32 ++++++------- 2 files changed, 54 insertions(+), 67 deletions(-) diff --git a/zjit/src/asm/x86_64/mod.rs b/zjit/src/asm/x86_64/mod.rs index ae965ccb235f6b..628a83b99cfe46 100644 --- a/zjit/src/asm/x86_64/mod.rs +++ b/zjit/src/asm/x86_64/mod.rs @@ -102,6 +102,10 @@ impl X86Reg { reg_no: self.reg_no } } + + pub fn rex_needed(&self) -> bool { + self.reg_no > 7 || self.num_bits == 8 && self.reg_no >= 4 + } } impl X86Opnd { @@ -110,7 +114,7 @@ impl X86Opnd { X86Opnd::None => false, X86Opnd::Imm(_) => false, X86Opnd::UImm(_) => false, - X86Opnd::Reg(reg) => reg.reg_no > 7 || reg.num_bits == 8 && reg.reg_no >= 4, + X86Opnd::Reg(reg) => reg.rex_needed(), X86Opnd::Mem(mem) => mem.base_reg_no > 7 || (mem.idx_reg_no.unwrap_or(0) > 7), X86Opnd::IPRel(_) => false } @@ -923,60 +927,43 @@ pub fn lea(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { /// mov - Data move operation pub fn mov(cb: &mut CodeBlock, dst: X86Opnd, src: X86Opnd) { - match (dst, src) { - // R + Imm - (X86Opnd::Reg(reg), X86Opnd::Imm(imm)) => { - assert!(imm.num_bits <= reg.num_bits); - - // In case the source immediate could be zero extended to be 64 - // bit, we can use the 32-bit operands version of the instruction. - // For example, we can turn mov(rax, 0x34) into the equivalent - // mov(eax, 0x34). - if (reg.num_bits == 64) && (imm.value > 0) && (imm.num_bits <= 32) { - if dst.rex_needed() { - write_rex(cb, false, 0, 0, reg.reg_no); - } - write_opcode(cb, 0xB8, reg); - cb.write_int(imm.value as u64, 32); - } else { - if reg.num_bits == 16 { - cb.write_byte(0x66); - } - - if dst.rex_needed() || reg.num_bits == 64 { - write_rex(cb, reg.num_bits == 64, 0, 0, reg.reg_no); - } + fn emit_reg_imm(cb: &mut CodeBlock, reg: X86Reg, imm: X86Imm) { + // In case the source immediate could be zero extended to be 64 + // bit, we can use the 32-bit operands version of the instruction. + // For example, we can turn mov(rax, 0x34) into the equivalent + // mov(eax, 0x34). + if (reg.num_bits == 64) && u32::try_from(imm.value).is_ok() { + if reg.rex_needed() { + write_rex(cb, false, 0, 0, reg.reg_no); + } + write_opcode(cb, 0xB8, reg); + cb.write_int(imm.value as u64, 32); + } else if reg.num_bits == 64 && imm.num_bits <= 32 { + // Use 32-to-64 bit sign-extension when possible + write_rm(cb, false, true /* REX.w */, X86Opnd::None, X86Opnd::Reg(reg), Some(0) /* /0 */, &[0xc7]); + cb.write_int(imm.value as u64, 32); + } else { + if reg.num_bits == 16 { + cb.write_byte(0x66); + } - write_opcode(cb, if reg.num_bits == 8 { 0xb0 } else { 0xb8 }, reg); - cb.write_int(imm.value as u64, reg.num_bits.into()); + if reg.rex_needed() || reg.num_bits == 64 { + write_rex(cb, reg.num_bits == 64, 0, 0, reg.reg_no); } - }, + + write_opcode(cb, if reg.num_bits == 8 { 0xb0 } else { 0xb8 }, reg); + cb.write_int(imm.value as u64, reg.num_bits.into()); + } + } + match (dst, src) { + // R + Imm + (X86Opnd::Reg(reg), X86Opnd::Imm(imm)) => emit_reg_imm(cb, reg, imm), // R + UImm (X86Opnd::Reg(reg), X86Opnd::UImm(uimm)) => { - assert!(uimm.num_bits <= reg.num_bits); - - // In case the source immediate could be zero extended to be 64 - // bit, we can use the 32-bit operands version of the instruction. - // For example, we can turn mov(rax, 0x34) into the equivalent - // mov(eax, 0x34). - if (reg.num_bits == 64) && (uimm.value <= u32::MAX.into()) { - if dst.rex_needed() { - write_rex(cb, false, 0, 0, reg.reg_no); - } - write_opcode(cb, 0xB8, reg); - cb.write_int(uimm.value, 32); - } else { - if reg.num_bits == 16 { - cb.write_byte(0x66); - } - - if dst.rex_needed() || reg.num_bits == 64 { - write_rex(cb, reg.num_bits == 64, 0, 0, reg.reg_no); - } - - write_opcode(cb, if reg.num_bits == 8 { 0xb0 } else { 0xb8 }, reg); - cb.write_int(uimm.value, reg.num_bits.into()); - } + // u64->i64 type cast is a bit pattern no-op + let value: u64 = uimm.value; + let value = value as i64; + emit_reg_imm(cb, reg, X86Imm { num_bits: imm_num_bits(value), value }); }, // M + Imm (X86Opnd::Mem(mem), X86Opnd::Imm(imm)) => { diff --git a/zjit/src/asm/x86_64/tests.rs b/zjit/src/asm/x86_64/tests.rs index 35bd3b005d4a15..0dfee2649669b9 100644 --- a/zjit/src/asm/x86_64/tests.rs +++ b/zjit/src/asm/x86_64/tests.rs @@ -373,13 +373,13 @@ fn test_mov() { 0x0: mov edx, dword ptr [rbx + 0x80] 0x0: mov rax, qword ptr [rsp + 4] 0x0: mov r8d, 0x34 - 0x0: movabs r8, 0x80000000 - 0x0: movabs r8, 0xffffffffffffffff + 0x0: mov r8d, 0x80000000 + 0x0: mov r8, 0xffffffffffffffff 0x0: mov eax, 0x34 0x0: movabs rax, 0xffc0000000000002 - 0x0: movabs rax, 0x80000000 - 0x0: movabs rax, 0xffffffffffffffcc - 0x0: movabs rax, 0xffffffffffffffff + 0x0: mov eax, 0x80000000 + 0x0: mov rax, 0xffffffffffffffcc + 0x0: mov rax, 0xffffffffffffffff 0x0: mov cl, r9b 0x0: mov rbx, rax 0x0: mov rdi, rbx @@ -405,13 +405,13 @@ fn test_mov() { 8b9380000000 488b442404 41b834000000 - 49b80000008000000000 - 49b8ffffffffffffffff + 41b800000080 + 49c7c0ffffffff b834000000 48b8020000000000c0ff - 48b80000008000000000 - 48b8ccffffffffffffff - 48b8ffffffffffffffff + b800000080 + 48c7c0ccffffff + 48c7c0ffffffff 4488c9 4889c3 4889df @@ -488,8 +488,8 @@ fn test_mov_unsigned() { 0x0: mov eax, 1 0x0: mov eax, 0xffffffff 0x0: movabs rax, 0x100000000 - 0x0: movabs rax, 0xffffffffffffffff - 0x0: movabs r8, 0xffffffffffffffff + 0x0: mov rax, 0xffffffffffffffff + 0x0: mov r8, 0xffffffffffffffff 0x0: mov r8b, 1 0x0: mov r8b, 0xff 0x0: mov r8w, 1 @@ -497,7 +497,7 @@ fn test_mov_unsigned() { 0x0: mov r8d, 1 0x0: mov r8d, 0xffffffff 0x0: mov r8d, 1 - 0x0: movabs r8, 0xffffffffffffffff + 0x0: mov r8, 0xffffffffffffffff "); assert_snapshot!(hexdumps!(cb01, cb02, cb03, cb04, cb05, cb06, cb07, cb08, cb09, cb10, cb11, cb12, cb13, cb14, cb15, cb16, cb17, cb18, cb19, cb20, cb21), @" @@ -512,8 +512,8 @@ fn test_mov_unsigned() { b801000000 b8ffffffff 48b80000000001000000 - 48b8ffffffffffffffff - 49b8ffffffffffffffff + 48c7c0ffffffff + 49c7c0ffffffff 41b001 41b0ff 6641b80100 @@ -521,7 +521,7 @@ fn test_mov_unsigned() { 41b801000000 41b8ffffffff 41b801000000 - 49b8ffffffffffffffff + 49c7c0ffffffff "); } From 5ec634bb4e14251eb18b2b762543571038cc6fa8 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Tue, 19 May 2026 18:37:28 +0200 Subject: [PATCH 2/6] [DOC] nodoc various classes under `Enumerator` They are implementation details: * https://docs.ruby-lang.org/en/4.0/Enumerator/Yielder.html * https://docs.ruby-lang.org/en/4.0/Enumerator/Producer.html * https://docs.ruby-lang.org/en/4.0/Enumerator/Generator.html --- enum.c | 2 +- enumerator.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/enum.c b/enum.c index 9b20451207a25a..a2941dd7dd5b38 100644 --- a/enum.c +++ b/enum.c @@ -5147,7 +5147,7 @@ enum_compact(VALUE obj) * end * * The result of the size function should represent the number of iterations - * (i.e., the number of times Enumerator::Yielder#yield is called). + * (i.e., the number of times you yield to the block argument). * In the above example, the block calls #yield three times, and * the size function, +-> { 3 }+, returns 3 accordingly. * The result of the size function can be an integer, +Float::INFINITY+, diff --git a/enumerator.c b/enumerator.c index 166e8e697ec855..69c96b2d8f0a32 100644 --- a/enumerator.c +++ b/enumerator.c @@ -4706,7 +4706,7 @@ InitVM_Enumerator(void) rb_eStopIteration = rb_define_class("StopIteration", rb_eIndexError); rb_define_method(rb_eStopIteration, "result", stop_result, 0); - /* Generator */ + /* :nodoc: Generator */ rb_cGenerator = rb_define_class_under(rb_cEnumerator, "Generator", rb_cObject); rb_include_module(rb_cGenerator, rb_mEnumerable); rb_define_alloc_func(rb_cGenerator, generator_allocate); @@ -4714,7 +4714,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cGenerator, "initialize_copy", generator_init_copy, 1); rb_define_method(rb_cGenerator, "each", generator_each, -1); - /* Yielder */ + /* :nodoc: Yielder */ rb_cYielder = rb_define_class_under(rb_cEnumerator, "Yielder", rb_cObject); rb_define_alloc_func(rb_cYielder, yielder_allocate); rb_define_method(rb_cYielder, "initialize", yielder_initialize, 0); @@ -4722,7 +4722,7 @@ InitVM_Enumerator(void) rb_define_method(rb_cYielder, "<<", yielder_yield_push, 1); rb_define_method(rb_cYielder, "to_proc", yielder_to_proc, 0); - /* Producer */ + /* :nodoc: Producer */ rb_cEnumProducer = rb_define_class_under(rb_cEnumerator, "Producer", rb_cObject); rb_define_alloc_func(rb_cEnumProducer, producer_allocate); rb_define_method(rb_cEnumProducer, "each", producer_each, 0); From 3373fcc2dee7c4560d2c3e4280c549cdb1b5de63 Mon Sep 17 00:00:00 2001 From: himura467 Date: Wed, 20 May 2026 00:50:08 +0900 Subject: [PATCH 3/6] Fix UAF in IO::Buffer#~ when self is an invalidated slice `io_buffer_not` accessed `buffer->base` directly without validating that the buffer was still live. A slice whose parent had been freed retained its stale base pointer, so calling `~` on it caused a UAF. Use `io_buffer_get_bytes_for_reading`, which raises `IO::Buffer::InvalidatedError` before any memory access if the buffer has been invalidated. --- io_buffer.c | 8 ++++++-- test/ruby/test_io_buffer.rb | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/io_buffer.c b/io_buffer.c index 95d6b2491397fb..faa53042481144 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -3554,11 +3554,15 @@ io_buffer_not(VALUE self) struct rb_io_buffer *buffer = NULL; TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer); - VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size)); + const void *base; + size_t size; + io_buffer_get_bytes_for_reading(buffer, &base, &size); + + VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size)); struct rb_io_buffer *output_buffer = NULL; TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer); - memory_not(output_buffer->base, buffer->base, buffer->size); + memory_not(output_buffer->base, base, size); return output; } diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 4022bd5d4873cf..b6372f25b88ef6 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -711,6 +711,7 @@ def test_operators_raise_on_freed_self assert_raise(IO::Buffer::InvalidatedError) { slice & mask } assert_raise(IO::Buffer::InvalidatedError) { slice | mask } assert_raise(IO::Buffer::InvalidatedError) { slice ^ mask } + assert_raise(IO::Buffer::InvalidatedError) { ~slice } end def test_operators_raise_on_freed_mask From cb0cd76a086d2d9423adaa4c5032a5fa149e7ef1 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 15 May 2026 22:03:36 -0700 Subject: [PATCH 4/6] Handle refinements correctly in {Method,UnboundMethod}#super_method A `Method#super_method` chain should return the methods that `super` would call if the method was called normally. Previously, there were multiple problems: * There was an infinite `super_method` loop for refined methods. * Lookup considered the refinements activated at the call site of `super_method`, and not all the call site of `super` inside the method. This tries to recreate the logic that `super` uses inside `super_method`. It avoids the loop. It also considers the refinements activated for the method itself, not for the caller of `super_method`, correctly handling refinements in outer scopes of the method. This requires avoiding the use of rb_callable_method_entry_with_refinements, which implicitly will consider refinements activated in the caller of `super_method`. The added tests attempt to ensure that the `super_method` lookup chain matches the methods that `super` calls if you call the method. This adds an `RICLASS_FOR_REFINEMENT_P` helper method, for logic that is used a couple times in the new code and once in `vm_search_normal_superclass`. --- internal/class.h | 8 ++ proc.c | 58 ++++++++- test/ruby/test_refinement.rb | 228 +++++++++++++++++++++++++++++++++++ vm_insnhelper.c | 4 +- 4 files changed, 293 insertions(+), 5 deletions(-) diff --git a/internal/class.h b/internal/class.h index e02e1a408bdf18..4366223f84f654 100644 --- a/internal/class.h +++ b/internal/class.h @@ -646,6 +646,14 @@ RICLASS_OWNS_M_TBL_P(VALUE iclass) return RCLASSEXT_ICLASS_IS_ORIGIN(ext) && !RCLASSEXT_ICLASS_ORIGIN_SHARED_MTBL(ext); } +static inline bool +RICLASS_FOR_REFINEMENT_P(VALUE iclass) +{ + return BUILTIN_TYPE(iclass) == T_ICLASS && + RB_TYPE_P(RBASIC(iclass)->klass, T_MODULE) && + FL_TEST_RAW(RBASIC(iclass)->klass, RMODULE_IS_REFINEMENT); +} + static inline void RCLASS_SET_INCLUDER(VALUE iclass, VALUE klass) { diff --git a/proc.c b/proc.c index 563f2393f3d2c9..698c50b5178e35 100644 --- a/proc.c +++ b/proc.c @@ -3595,6 +3595,7 @@ method_to_proc(VALUE method) } extern VALUE rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner); +extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); /* * call-seq: @@ -3621,11 +3622,64 @@ method_super_method(VALUE method) mid = data->me->def->body.alias.original_me->def->original_id; } else { - super_class = RCLASS_SUPER(RCLASS_ORIGIN(iclass)); + VALUE klass = iclass; + if (RICLASS_FOR_REFINEMENT_P(klass)) { + // Refined methods need this check before superclass determination + klass = RBASIC(klass)->klass; + } + super_class = RCLASS_SUPER(RCLASS_ORIGIN(klass)); mid = data->me->def->original_id; } if (!super_class) return Qnil; - me = (rb_method_entry_t *)rb_callable_method_entry_with_refinements(super_class, mid, &iclass); + + // For refined methods, skip refinements for the same definition, but consider + // refinements for superclass methods + const rb_method_definition_t *skip_def = RICLASS_FOR_REFINEMENT_P(iclass) ? data->me->def : NULL; + + // Use the CREF of the Method/UnboundMethod, not the CREF of the caller of super_method. + // We must avoid the use of rb_callable_method_entry_with_refinements, as that will + // implicitly use the refinements activated in of the caller of super_method. + const rb_cref_t *cref = NULL; + if (data->me->def->type == VM_METHOD_TYPE_ISEQ) { + cref = data->me->def->body.iseq.cref; + } + VALUE klass = super_class; + me = NULL; + while (klass) { + const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); + if (!cme) break; + if (cme->def->type != VM_METHOD_TYPE_REFINED) { + me = (rb_method_entry_t *)cme; + iclass = cme->defined_class; + break; + } + // Look through all CREF scopes for a refinement for cme->owner, mirroring + // the loop in search_refined_method. + const rb_cref_t *c; + for (c = cref; c; c = CREF_NEXT(c)) { + VALUE refs = CREF_REFINEMENTS(c); + if (NIL_P(refs)) continue; + VALUE r = rb_hash_lookup(refs, cme->owner); + if (NIL_P(r)) continue; + const rb_callable_method_entry_t *ref_cme = rb_callable_method_entry(r, mid); + if (!ref_cme) break; + if (ref_cme->def->type == VM_METHOD_TYPE_REFINED) continue; + if (skip_def && rb_method_definition_eq(ref_cme->def, skip_def)) continue; + me = (rb_method_entry_t *)ref_cme; + iclass = ref_cme->defined_class; + break; + } + if (me) break; + // No refined method found. Use orig_me if available, or normal method lookup + // in superclass otherwise. + const rb_method_entry_t *orig_me = cme->def->body.refined.orig_me; + if (orig_me) { + me = (rb_method_entry_t *)orig_me; + iclass = orig_me->defined_class ? orig_me->defined_class : cme->defined_class; + break; + } + klass = RCLASS_SUPER(cme->defined_class); + } if (!me) return Qnil; return mnew_internal(me, me->owner, iclass, data->recv, mid, rb_obj_class(method), FALSE, FALSE); } diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 5089135043929e..1c2f406e11f871 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -3403,6 +3403,234 @@ class RefinedScope RUBY end + def test_method_super_method_single_refinements + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + using M + a = A.new + m = a.method(:b) + assert_equal("MA", a.b) + assert_equal("MA", m.call) + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_multiple_refinements_with_activated_refinements_during_super + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + module N + using M + R = refine(A) { def b; "N" + super; end } + end + using M + using N + a = A.new + m = a.method(:b) + assert_equal("NMA", a.b) + assert_equal("NMA", m.call) + assert_equal(N::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_multiple_refinements_without_activated_refinements_during_super + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + module N + R = refine(A) { def b; "N" + super; end } + end + using M + using N + a = A.new + m = a.method(:b) + assert_equal("NA", a.b) + assert_equal("NA", m.call) + assert_equal(N::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_unbound_method_super_method_single_refinements + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + using M + m = A.instance_method(:b) + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_nonrefined_finds_refined_super + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + using M + class B < A + def b = "B" + super + end + b = B.new + m = b.method(:b) + assert_equal("BMA", b.b) + assert_equal("BMA", m.call) + assert_equal(B, m.owner) + m = m.super_method + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_refined_finds_refined_method_in_superclass + assert_separately([], <<~RUBY) + class A + def b = "A" + end + class B < A + end + module M + R = refine(A) { def b; "M" + super; end } + end + using M + module N + R = refine(B) { def b; "N" + super; end } + end + using N + + b = B.new + m = b.method(:b) + assert_equal("NMA", b.b) + assert_equal("NMA", m.call) + assert_equal(N::R, m.owner) + assert_equal(B, m.owner.target) + + m = m.super_method + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_uses_cref_of_method_not_cref_of_caller + assert_separately([], <<~RUBY) + class A + def b = "A" + end + class B < A + end + module M + R = refine(A) { def b; "M" + super; end } + end + module N + R = refine(B) do + using M + def b; "N" + super; end + end + end + using N + + b = B.new + m = b.method(:b) + assert_equal("NMA", b.b) + assert_equal("NMA", m.call) + assert_equal(N::R, m.owner) + assert_equal(B, m.owner.target) + + m = m.super_method + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + + def test_method_super_method_only_considers_activated_refinements + assert_separately([], <<~RUBY) + class A + def b = "A" + end + class B < A + def b = "B" + super + end + module M + R = refine(A){def b = "M" + super} + end + module N + R = refine(B){def b = "N" + super} + end + + module O + using M + using N + + b = B.new + m = b.method(:b) + assert_equal("NBA", b.b) + assert_equal("NBA", m.call) + assert_equal(N::R, m.owner) + assert_equal(B, m.owner.target) + + m = m.super_method + assert_equal(B, m.owner) + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + end + RUBY + end + private def eval_using(mod, s) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index aebe9521f5b87a..783246a7efc513 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4980,9 +4980,7 @@ vm_call_super_method(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, st static inline VALUE vm_search_normal_superclass(VALUE klass) { - if (BUILTIN_TYPE(klass) == T_ICLASS && - RB_TYPE_P(RBASIC(klass)->klass, T_MODULE) && - FL_TEST_RAW(RBASIC(klass)->klass, RMODULE_IS_REFINEMENT)) { + if (RICLASS_FOR_REFINEMENT_P(klass)) { klass = RBASIC(klass)->klass; } klass = RCLASS_ORIGIN(klass); From 8f8dd01a93b247f607d927770a2ad09e1e0a1647 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Mon, 18 May 2026 21:52:36 -0700 Subject: [PATCH 5/6] Correctly handle refinements in Method#super_method for bmethods Bmethods need to use a different approach to find the cref to use in order to determine the refinements in effect. --- proc.c | 15 ++++++++++++++- test/ruby/test_refinement.rb | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/proc.c b/proc.c index 698c50b5178e35..efbb11fd9fd68d 100644 --- a/proc.c +++ b/proc.c @@ -3596,6 +3596,7 @@ method_to_proc(VALUE method) extern VALUE rb_find_defined_class_by_owner(VALUE current_class, VALUE target_owner); extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); +rb_cref_t * rb_vm_get_cref(const VALUE *ep); /* * call-seq: @@ -3640,8 +3641,20 @@ method_super_method(VALUE method) // We must avoid the use of rb_callable_method_entry_with_refinements, as that will // implicitly use the refinements activated in of the caller of super_method. const rb_cref_t *cref = NULL; - if (data->me->def->type == VM_METHOD_TYPE_ISEQ) { + switch (data->me->def->type) { + case VM_METHOD_TYPE_ISEQ: cref = data->me->def->body.iseq.cref; + break; + case VM_METHOD_TYPE_BMETHOD: { + const rb_proc_t *proc; + GetProcPtr(data->me->def->body.bmethod.proc, proc); + const struct rb_block *block = &proc->block; + if (vm_block_type(block) == block_type_iseq) + cref = rb_vm_get_cref(block->as.captured.ep); + break; + } + default: + break; } VALUE klass = super_class; me = NULL; diff --git a/test/ruby/test_refinement.rb b/test/ruby/test_refinement.rb index 1c2f406e11f871..dce09c2dd8d0bd 100644 --- a/test/ruby/test_refinement.rb +++ b/test/ruby/test_refinement.rb @@ -3631,6 +3631,35 @@ module O RUBY end + def test_method_super_method_bmethod_finds_refinements + assert_separately([], <<~RUBY) + class A + def b = "A" + end + module M + R = refine(A) { def b; "M" + super; end } + end + using M + class B < A + define_method(:b) { "B" + super() } + end + + b = B.new + m = b.method(:b) + assert_equal("BMA", b.b) + assert_equal("BMA", m.call) + assert_equal(B, m.owner) + + m = m.super_method + assert_equal(M::R, m.owner) + assert_equal(A, m.owner.target) + + m = m.super_method + assert_equal(A, m.owner) + assert_nil(m.super_method) + RUBY + end + private def eval_using(mod, s) From 8601f5a42eecadcc39b483a072b18b29a3a4e23c Mon Sep 17 00:00:00 2001 From: Misaki Shioi Date: Wed, 18 Mar 2026 12:37:14 +0900 Subject: [PATCH 6/6] Fix reading freed memory in rb_getaddrinfo --- ext/socket/raddrinfo.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 6cdf5c6abc40e7..f1fbcc35def3e2 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -601,13 +601,9 @@ rb_getaddrinfo(const char *hostp, const char *portp, const struct addrinfo *hint if (need_free) free_getaddrinfo_arg(arg); if (timedout) { - if (arg->ai) { - rsock_raise_user_specified_timeout(arg->ai, Qnil, Qnil); - } else { - VALUE host = rb_str_new_cstr(hostp); - VALUE port = rb_str_new_cstr(portp); - rsock_raise_user_specified_timeout(NULL, host, port); - } + VALUE host = rb_str_new_cstr(hostp); + VALUE port = rb_str_new_cstr(portp); + rsock_raise_user_specified_timeout(NULL, host, port); } // If the current thread is interrupted by asynchronous exception, the following raises the exception.