From 6ea811e67c46eb04d0b48c2c674ea89f4d1a24d5 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 4 Mar 2026 16:18:38 -0500 Subject: [PATCH 1/7] ZJIT: Add for_each_operand methods to Insn (#16305) - Add `for_each_operand`, `for_each_operand_mut`, and `try_for_each_operand` methods to `Insn`, backed by a shared `for_each_operand_impl!` macro - Replace the old `worklist_traverse_single_insn` function on `Function` with direct use of the new methods - Avoid materializing resolved `Insn` objects at call sites; instead apply union-find inside the callback --- zjit/src/hir.rs | 601 +++++++++++++++++++++++++----------------------- 1 file changed, 316 insertions(+), 285 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7487f8c3e3d26e..7d8e6041717117 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1069,6 +1069,282 @@ pub enum Insn { CheckInterrupts { state: InsnId }, } +/// Macro that enumerates all operands of an Insn, dispatching to caller-provided +/// `$visit_one` macro for a single InsnId field and `$visit_many` macro for a +/// slice/Vec of InsnIds. Used by both `for_each_operand` and `for_each_operand_mut`. +macro_rules! for_each_operand_impl { + ($self:expr, $visit_one:ident, $visit_many:ident) => { + match $self { + Insn::Const { .. } + | Insn::Param + | Insn::LoadArg { .. } + | Insn::Entries { .. } + | Insn::EntryPoint { .. } + | Insn::LoadPC + | Insn::LoadSP + | Insn::LoadEC + | Insn::GetEP { .. } + | Insn::LoadSelf + | Insn::PutSpecialObject { .. } + | Insn::IncrCounter(_) + | Insn::IncrCounterPtr { .. } => {} + + Insn::IsBlockGiven { lep } => { + $visit_one!(lep); + } + Insn::IsBlockParamModified { ep } => { + $visit_one!(ep); + } + Insn::PatchPoint { state, .. } + | Insn::CheckInterrupts { state } + | Insn::GetBlockParam { state, .. } + | Insn::GetConstantPath { state, .. } => { + $visit_one!(state); + } + Insn::FixnumBitCheck { val, .. } => { + $visit_one!(val); + } + Insn::ArrayMax { elements, state, .. } + | Insn::ArrayHash { elements, state, .. } + | Insn::NewHash { elements, state, .. } + | Insn::NewArray { elements, state, .. } => { + $visit_many!(elements); + $visit_one!(state); + } + Insn::ArrayInclude { elements, target, state, .. } => { + $visit_many!(elements); + $visit_one!(target); + $visit_one!(state); + } + Insn::ArrayPackBuffer { elements, fmt, buffer, state, .. } => { + $visit_many!(elements); + $visit_one!(fmt); + $visit_one!(buffer); + $visit_one!(state); + } + Insn::DupArrayInclude { target, state, .. } => { + $visit_one!(target); + $visit_one!(state); + } + Insn::NewRange { low, high, state, .. } + | Insn::NewRangeFixnum { low, high, state, .. } => { + $visit_one!(low); + $visit_one!(high); + $visit_one!(state); + } + Insn::StringConcat { strings, state, .. } => { + $visit_many!(strings); + $visit_one!(state); + } + Insn::StringGetbyte { string, index } => { + $visit_one!(string); + $visit_one!(index); + } + Insn::StringSetbyteFixnum { string, index, value } => { + $visit_one!(string); + $visit_one!(index); + $visit_one!(value); + } + Insn::StringAppend { recv, other, state } + | Insn::StringAppendCodepoint { recv, other, state } => { + $visit_one!(recv); + $visit_one!(other); + $visit_one!(state); + } + Insn::ToRegexp { values, state, .. } => { + $visit_many!(values); + $visit_one!(state); + } + Insn::RefineType { val, .. } + | Insn::HasType { val, .. } + | Insn::Return { val } + | Insn::Test { val } + | Insn::SetLocal { val, .. } + | Insn::BoxBool { val } + | Insn::IsNil { val } => { + $visit_one!(val); + } + Insn::SetGlobal { val, state, .. } + | Insn::Defined { v: val, state, .. } + | Insn::StringIntern { val, state } + | Insn::StringCopy { val, state, .. } + | Insn::ObjectAlloc { val, state } + | Insn::GuardType { val, state, .. } + | Insn::GuardTypeNot { val, state, .. } + | Insn::GuardBitEquals { val, state, .. } + | Insn::GuardAnyBitSet { val, state, .. } + | Insn::GuardNoBitsSet { val, state, .. } + | Insn::ToArray { val, state } + | Insn::IsMethodCfunc { val, state, .. } + | Insn::ToNewArray { val, state } + | Insn::BoxFixnum { val, state } => { + $visit_one!(val); + $visit_one!(state); + } + Insn::GuardGreaterEq { left, right, state, .. } + | Insn::GuardLess { left, right, state } => { + $visit_one!(left); + $visit_one!(right); + $visit_one!(state); + } + Insn::Snapshot { state } => { + $visit_many!(state.stack); + $visit_many!(state.locals); + } + Insn::FixnumAdd { left, right, state } + | Insn::FixnumSub { left, right, state } + | Insn::FixnumMult { left, right, state } + | Insn::FixnumDiv { left, right, state } + | Insn::FixnumMod { left, right, state } + | Insn::ArrayExtend { left, right, state } + | Insn::FixnumLShift { left, right, state } => { + $visit_one!(left); + $visit_one!(right); + $visit_one!(state); + } + Insn::FixnumLt { left, right } + | Insn::FixnumLe { left, right } + | Insn::FixnumGt { left, right } + | Insn::FixnumGe { left, right } + | Insn::FixnumEq { left, right } + | Insn::FixnumNeq { left, right } + | Insn::FixnumAnd { left, right } + | Insn::FixnumOr { left, right } + | Insn::FixnumXor { left, right } + | Insn::FixnumRShift { left, right } + | Insn::IsBitEqual { left, right } + | Insn::IsBitNotEqual { left, right } => { + $visit_one!(left); + $visit_one!(right); + } + Insn::Jump(BranchEdge { args, .. }) => { + $visit_many!(args); + } + Insn::IfTrue { val, target: BranchEdge { args, .. } } + | Insn::IfFalse { val, target: BranchEdge { args, .. } } => { + $visit_one!(val); + $visit_many!(args); + } + Insn::ArrayDup { val, state } + | Insn::Throw { val, state, .. } + | Insn::HashDup { val, state } => { + $visit_one!(val); + $visit_one!(state); + } + Insn::ArrayAref { array, index } => { + $visit_one!(array); + $visit_one!(index); + } + Insn::ArrayAset { array, index, val } => { + $visit_one!(array); + $visit_one!(index); + $visit_one!(val); + } + Insn::ArrayPop { array, state } => { + $visit_one!(array); + $visit_one!(state); + } + Insn::ArrayLength { array } => { + $visit_one!(array); + } + Insn::HashAref { hash, key, state } => { + $visit_one!(hash); + $visit_one!(key); + $visit_one!(state); + } + Insn::HashAset { hash, key, val, state } => { + $visit_one!(hash); + $visit_one!(key); + $visit_one!(val); + $visit_one!(state); + } + Insn::Send { recv, args, state, .. } + | Insn::SendForward { recv, args, state, .. } + | Insn::CCallVariadic { recv, args, state, .. } + | Insn::CCallWithFrame { recv, args, state, .. } + | Insn::SendDirect { recv, args, state, .. } + | Insn::InvokeBuiltin { recv, args, state, .. } + | Insn::InvokeSuper { recv, args, state, .. } + | Insn::InvokeSuperForward { recv, args, state, .. } + | Insn::InvokeProc { recv, args, state, .. } => { + $visit_one!(recv); + $visit_many!(args); + $visit_one!(state); + } + Insn::InvokeBlock { args, state, .. } => { + $visit_many!(args); + $visit_one!(state); + } + Insn::CCall { recv, args, .. } => { + $visit_one!(recv); + $visit_many!(args); + } + Insn::GetIvar { self_val, state, .. } + | Insn::DefinedIvar { self_val, state, .. } => { + $visit_one!(self_val); + $visit_one!(state); + } + Insn::GetConstant { klass, allow_nil, state, .. } => { + $visit_one!(klass); + $visit_one!(allow_nil); + $visit_one!(state); + } + Insn::SetIvar { self_val, val, state, .. } => { + $visit_one!(self_val); + $visit_one!(val); + $visit_one!(state); + } + Insn::GetClassVar { state, .. } => { + $visit_one!(state); + } + Insn::SetClassVar { val, state, .. } => { + $visit_one!(val); + $visit_one!(state); + } + Insn::ArrayPush { array, val, state } => { + $visit_one!(array); + $visit_one!(val); + $visit_one!(state); + } + Insn::ObjToString { val, state, .. } => { + $visit_one!(val); + $visit_one!(state); + } + Insn::AnyToString { val, str, state, .. } => { + $visit_one!(val); + $visit_one!(str); + $visit_one!(state); + } + Insn::LoadField { recv, .. } => { + $visit_one!(recv); + } + Insn::StoreField { recv, val, .. } + | Insn::WriteBarrier { recv, val } => { + $visit_one!(recv); + $visit_one!(val); + } + Insn::GetGlobal { state, .. } + | Insn::GetSpecialSymbol { state, .. } + | Insn::GetSpecialNumber { state, .. } + | Insn::ObjectAllocClass { state, .. } + | Insn::SideExit { state, .. } => { + $visit_one!(state); + } + Insn::UnboxFixnum { val } => { + $visit_one!(val); + } + Insn::FixnumAref { recv, index } => { + $visit_one!(recv); + $visit_one!(index); + } + Insn::IsA { val, class } => { + $visit_one!(val); + $visit_one!(class); + } + } + }; +} + impl Insn { /// Not every instruction returns a value. Return true if the instruction does and false otherwise. pub fn has_output(&self) -> bool { @@ -1102,6 +1378,28 @@ impl Insn { } } + /// Call `f` on each operand (InsnId) of this instruction. + pub fn for_each_operand(&self, mut f: impl FnMut(InsnId)) { + macro_rules! visit_one { ($id:expr) => { f(*$id) }; } + macro_rules! visit_many { ($s:expr) => { for id in ($s).iter() { f(*id) } }; } + for_each_operand_impl!(self, visit_one, visit_many); + } + + /// Call `f` on a mutable reference to each operand (InsnId) of this instruction. + pub fn for_each_operand_mut(&mut self, mut f: impl FnMut(&mut InsnId)) { + macro_rules! visit_one { ($id:expr) => { f($id) }; } + macro_rules! visit_many { ($s:expr) => { for id in ($s).iter_mut() { f(id) } }; } + for_each_operand_impl!(self, visit_one, visit_many); + } + + /// Call `f` on each operand, short-circuiting on the first error. + pub fn try_for_each_operand(&self, mut f: impl FnMut(InsnId) -> Result<(), E>) -> Result<(), E> { + macro_rules! visit_one { ($id:expr) => { f(*$id)? }; } + macro_rules! visit_many { ($s:expr) => { for id in ($s).iter() { f(*id)? } }; } + for_each_operand_impl!(self, visit_one, visit_many); + Ok(()) + } + pub fn print<'a>(&self, ptr_map: &'a PtrPrintMap, iseq: Option) -> InsnPrinter<'a> { InsnPrinter { inner: self.clone(), ptr_map, iseq } } @@ -4827,274 +5125,6 @@ impl Function { } } - fn worklist_traverse_single_insn(&self, insn: &Insn, worklist: &mut VecDeque) { - match insn { - &Insn::Const { .. } - | &Insn::Param - | &Insn::LoadArg { .. } - | &Insn::Entries { .. } - | &Insn::EntryPoint { .. } - | &Insn::LoadPC - | &Insn::LoadSP - | &Insn::LoadEC - | &Insn::GetEP { .. } - | &Insn::LoadSelf - | &Insn::PutSpecialObject { .. } - | &Insn::IncrCounter(_) - | &Insn::IncrCounterPtr { .. } => - {} - | &Insn::IsBlockGiven { lep } => { - worklist.push_back(lep); - } - &Insn::IsBlockParamModified { ep } => { - worklist.push_back(ep); - } - &Insn::PatchPoint { state, .. } - | &Insn::CheckInterrupts { state } - | &Insn::GetBlockParam { state, .. } - | &Insn::GetConstantPath { ic: _, state } => { - worklist.push_back(state); - } - &Insn::FixnumBitCheck { val, index: _ } => { - worklist.push_back(val) - } - &Insn::ArrayMax { ref elements, state } - | &Insn::ArrayHash { ref elements, state } - | &Insn::NewHash { ref elements, state } - | &Insn::NewArray { ref elements, state } => { - worklist.extend(elements); - worklist.push_back(state); - } - &Insn::ArrayInclude { ref elements, target, state } => { - worklist.extend(elements); - worklist.push_back(target); - worklist.push_back(state); - } - &Insn::ArrayPackBuffer { ref elements, fmt, buffer, state } => { - worklist.extend(elements); - worklist.push_back(fmt); - worklist.push_back(buffer); - worklist.push_back(state); - } - &Insn::DupArrayInclude { target, state, .. } => { - worklist.push_back(target); - worklist.push_back(state); - } - &Insn::NewRange { low, high, state, .. } - | &Insn::NewRangeFixnum { low, high, state, .. } => { - worklist.push_back(low); - worklist.push_back(high); - worklist.push_back(state); - } - &Insn::StringConcat { ref strings, state, .. } => { - worklist.extend(strings); - worklist.push_back(state); - } - &Insn::StringGetbyte { string, index } => { - worklist.push_back(string); - worklist.push_back(index); - } - &Insn::StringSetbyteFixnum { string, index, value } => { - worklist.push_back(string); - worklist.push_back(index); - worklist.push_back(value); - } - &Insn::StringAppend { recv, other, state } - | &Insn::StringAppendCodepoint { recv, other, state } - => { - worklist.push_back(recv); - worklist.push_back(other); - worklist.push_back(state); - } - &Insn::ToRegexp { ref values, state, .. } => { - worklist.extend(values); - worklist.push_back(state); - } - | &Insn::RefineType { val, .. } - | &Insn::HasType { val, .. } - | &Insn::Return { val } - | &Insn::Test { val } - | &Insn::SetLocal { val, .. } - | &Insn::BoxBool { val } - | &Insn::IsNil { val } => - worklist.push_back(val), - &Insn::SetGlobal { val, state, .. } - | &Insn::Defined { v: val, state, .. } - | &Insn::StringIntern { val, state } - | &Insn::StringCopy { val, state, .. } - | &Insn::ObjectAlloc { val, state } - | &Insn::GuardType { val, state, .. } - | &Insn::GuardTypeNot { val, state, .. } - | &Insn::GuardBitEquals { val, state, .. } - | &Insn::GuardAnyBitSet { val, state, .. } - | &Insn::GuardNoBitsSet { val, state, .. } - | &Insn::ToArray { val, state } - | &Insn::IsMethodCfunc { val, state, .. } - | &Insn::ToNewArray { val, state } - | &Insn::BoxFixnum { val, state } => { - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::GuardGreaterEq { left, right, state, .. } => { - worklist.push_back(left); - worklist.push_back(right); - worklist.push_back(state); - } - &Insn::GuardLess { left, right, state } => { - worklist.push_back(left); - worklist.push_back(right); - worklist.push_back(state); - } - Insn::Snapshot { state } => { - worklist.extend(&state.stack); - worklist.extend(&state.locals); - } - &Insn::FixnumAdd { left, right, state } - | &Insn::FixnumSub { left, right, state } - | &Insn::FixnumMult { left, right, state } - | &Insn::FixnumDiv { left, right, state } - | &Insn::FixnumMod { left, right, state } - | &Insn::ArrayExtend { left, right, state } - | &Insn::FixnumLShift { left, right, state } - => { - worklist.push_back(left); - worklist.push_back(right); - worklist.push_back(state); - } - &Insn::FixnumLt { left, right } - | &Insn::FixnumLe { left, right } - | &Insn::FixnumGt { left, right } - | &Insn::FixnumGe { left, right } - | &Insn::FixnumEq { left, right } - | &Insn::FixnumNeq { left, right } - | &Insn::FixnumAnd { left, right } - | &Insn::FixnumOr { left, right } - | &Insn::FixnumXor { left, right } - | &Insn::FixnumRShift { left, right } - | &Insn::IsBitEqual { left, right } - | &Insn::IsBitNotEqual { left, right } - => { - worklist.push_back(left); - worklist.push_back(right); - } - &Insn::Jump(BranchEdge { ref args, .. }) => worklist.extend(args), - &Insn::IfTrue { val, target: BranchEdge { ref args, .. } } | &Insn::IfFalse { val, target: BranchEdge { ref args, .. } } => { - worklist.push_back(val); - worklist.extend(args); - } - &Insn::ArrayDup { val, state } - | &Insn::Throw { val, state, .. } - | &Insn::HashDup { val, state } => { - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::ArrayAref { array, index } => { - worklist.push_back(array); - worklist.push_back(index); - } - &Insn::ArrayAset { array, index, val } => { - worklist.push_back(array); - worklist.push_back(index); - worklist.push_back(val); - } - &Insn::ArrayPop { array, state } => { - worklist.push_back(array); - worklist.push_back(state); - } - &Insn::ArrayLength { array } => { - worklist.push_back(array); - } - &Insn::HashAref { hash, key, state } => { - worklist.push_back(hash); - worklist.push_back(key); - worklist.push_back(state); - } - &Insn::HashAset { hash, key, val, state } => { - worklist.push_back(hash); - worklist.push_back(key); - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::Send { recv, ref args, state, .. } - | &Insn::SendForward { recv, ref args, state, .. } - | &Insn::CCallVariadic { recv, ref args, state, .. } - | &Insn::CCallWithFrame { recv, ref args, state, .. } - | &Insn::SendDirect { recv, ref args, state, .. } - | &Insn::InvokeBuiltin { recv, ref args, state, .. } - | &Insn::InvokeSuper { recv, ref args, state, .. } - | &Insn::InvokeSuperForward { recv, ref args, state, .. } - | &Insn::InvokeProc { recv, ref args, state, .. } => { - worklist.push_back(recv); - worklist.extend(args); - worklist.push_back(state); - } - &Insn::InvokeBlock { ref args, state, .. } => { - worklist.extend(args); - worklist.push_back(state) - } - &Insn::CCall { recv, ref args, .. } => { - worklist.push_back(recv); - worklist.extend(args); - } - &Insn::GetIvar { self_val, state, .. } | &Insn::DefinedIvar { self_val, state, .. } => { - worklist.push_back(self_val); - worklist.push_back(state); - } - &Insn::GetConstant { klass, allow_nil, state, .. } => { - worklist.push_back(klass); - worklist.push_back(allow_nil); - worklist.push_back(state); - } - &Insn::SetIvar { self_val, val, state, .. } => { - worklist.push_back(self_val); - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::GetClassVar { state, .. } => { - worklist.push_back(state); - } - &Insn::SetClassVar { val, state, .. } => { - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::ArrayPush { array, val, state } => { - worklist.push_back(array); - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::ObjToString { val, state, .. } => { - worklist.push_back(val); - worklist.push_back(state); - } - &Insn::AnyToString { val, str, state, .. } => { - worklist.push_back(val); - worklist.push_back(str); - worklist.push_back(state); - } - &Insn::LoadField { recv, .. } => { - worklist.push_back(recv); - } - &Insn::StoreField { recv, val, .. } - | &Insn::WriteBarrier { recv, val } => { - worklist.push_back(recv); - worklist.push_back(val); - } - &Insn::GetGlobal { state, .. } | - &Insn::GetSpecialSymbol { state, .. } | - &Insn::GetSpecialNumber { state, .. } | - &Insn::ObjectAllocClass { state, .. } | - &Insn::SideExit { state, .. } => worklist.push_back(state), - &Insn::UnboxFixnum { val } => worklist.push_back(val), - &Insn::FixnumAref { recv, index } => { - worklist.push_back(recv); - worklist.push_back(index); - } - &Insn::IsA { val, class } => { - worklist.push_back(val); - worklist.push_back(class); - } - } - } /// Remove instructions that do not have side effects and are not referenced by any other /// instruction. @@ -5115,7 +5145,10 @@ impl Function { while let Some(insn_id) = worklist.pop_front() { if necessary.get(insn_id) { continue; } necessary.insert(insn_id); - self.worklist_traverse_single_insn(&self.find(insn_id), &mut worklist); + let insn_id = self.union_find.borrow().find_const(insn_id); + self.insns[insn_id.0].for_each_operand(|operand| { + worklist.push_back(self.union_find.borrow().find_const(operand)); + }); } // Now remove all unnecessary instructions for block_id in &rpo { @@ -5384,10 +5417,10 @@ impl Function { let opcode = insn.print(&ptr_map, Some(self.iseq)).to_string(); - // Traverse the worklist to get inputs for a given instruction. - let mut inputs = VecDeque::new(); - self.worklist_traverse_single_insn(&insn, &mut inputs); - let inputs: Vec = inputs.into_iter().map(|x| x.0.into()).collect(); + // Collect inputs for a given instruction. + let mut inputs = Vec::new(); + insn.for_each_operand(|id| inputs.push(id.0.into())); + let inputs: Vec = inputs; instructions.push( Self::make_iongraph_instr( @@ -5625,15 +5658,14 @@ impl Function { } for &insn_id in &self.blocks[block.0].insns { let insn_id = self.union_find.borrow().find_const(insn_id); - let mut operands = VecDeque::new(); - let insn = self.find(insn_id); - self.worklist_traverse_single_insn(&insn, &mut operands); - for operand in operands { + self.insns[insn_id.0].try_for_each_operand(|operand| { + let operand = self.union_find.borrow().find_const(operand); if !assigned.get(operand) { return Err(ValidationError::OperandNotDefined(block, insn_id, operand)); } - } - if insn.has_output() { + Ok(()) + })?; + if self.insns[insn_id.0].has_output() { assigned.insert(insn_id); } } @@ -5653,15 +5685,14 @@ impl Function { // Check that each instruction's operands are assigned for &insn_id in &self.blocks[block.0].insns { let insn_id = self.union_find.borrow().find_const(insn_id); - let mut operands = VecDeque::new(); - let insn = self.find(insn_id); - self.worklist_traverse_single_insn(&insn, &mut operands); - for operand in operands { + self.insns[insn_id.0].try_for_each_operand(|operand| { + let operand = self.union_find.borrow().find_const(operand); if !assigned.get(operand) { return Err(ValidationError::OperandNotDefined(block, insn_id, operand)); } - } - if insn.has_output() { + Ok(()) + })?; + if self.insns[insn_id.0].has_output() { assigned.insert(insn_id); } } From d2a9bb69437ee054c6841dceec30238ec4604abc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 26 Nov 2025 22:30:27 +0900 Subject: [PATCH 2/7] [ruby/zlib] Fix buffer overflow at ungetc https://github.com/ruby/zlib/commit/608d2be66f --- ext/zlib/zlib.c | 4 +--- test/zlib/test_zlib.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 578ad108515d64..466a901c139753 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -860,9 +860,7 @@ zstream_buffer_ungets(struct zstream *z, const Bytef *b, unsigned long len) char *bufptr; long filled; - if (NIL_P(z->buf) || (long)rb_str_capacity(z->buf) <= ZSTREAM_BUF_FILLED(z)) { - zstream_expand_buffer_into(z, len); - } + zstream_expand_buffer_into(z, len); RSTRING_GETMEM(z->buf, bufptr, filled); memmove(bufptr + len, bufptr, filled); diff --git a/test/zlib/test_zlib.rb b/test/zlib/test_zlib.rb index 5b0bf9c9807ce7..48b8f172ff2bc2 100644 --- a/test/zlib/test_zlib.rb +++ b/test/zlib/test_zlib.rb @@ -882,6 +882,25 @@ def test_ungetc_at_start_of_file assert_equal(-1, r.pos, "[ruby-core:81488][Bug #13616]") end + def test_ungetc_buffer_underflow + initial_bufsize = 1024 + payload = "A" * initial_bufsize + gzip_io = StringIO.new + Zlib::GzipWriter.wrap(gzip_io) { |gz| gz.write(payload) } + compressed = gzip_io.string + + reader = Zlib::GzipReader.new(StringIO.new(compressed)) + reader.read(1) + overflow_bytes = "B" * (initial_bufsize) + reader.ungetc(overflow_bytes) + data = reader.read(overflow_bytes.bytesize) + assert_equal overflow_bytes.bytesize, data.bytesize, data + assert_empty data.delete("B"), data + data = reader.read() + assert_equal initial_bufsize - 1, data.bytesize, data + assert_empty data.delete("A"), data + end + def test_open Tempfile.create("test_zlib_gzip_reader_open") {|t| t.close From 48a210537de69c3ff2da1609ee8f2e3b94e2b536 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 26 Feb 2026 11:22:39 +0900 Subject: [PATCH 3/7] [ruby/zlib] Bump up to 3.2.3 https://github.com/ruby/zlib/commit/d9c7876988 --- ext/zlib/zlib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 466a901c139753..481d74b2b60e5f 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -25,7 +25,7 @@ # define VALGRIND_MAKE_MEM_UNDEFINED(p, n) 0 #endif -#define RUBY_ZLIB_VERSION "3.2.2" +#define RUBY_ZLIB_VERSION "3.2.3" #ifndef RB_PASS_CALLED_KEYWORDS # define rb_class_new_instance_kw(argc, argv, klass, kw_splat) rb_class_new_instance(argc, argv, klass) From c76ead9e21127622591c1bec215bb8cf21f0cbe6 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Mar 2026 01:39:42 +0000 Subject: [PATCH 4/7] Update default gems list at 48a210537de69c3ff2da1609ee8f2e [ci skip] --- NEWS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS.md b/NEWS.md index d9aaa08be6c754..353243add1f3f3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -76,6 +76,7 @@ releases. * strscan 3.1.7.dev * 3.1.6 to [v3.1.7][strscan-v3.1.7] * syntax_suggest 2.0.3 +* zlib 3.2.3 ### The following bundled gems are updated. From 97e27d7f30a0df4b0b67a619d5df4f1b2d1cca0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 02:11:19 +0000 Subject: [PATCH 5/7] Bump the github-actions group across 1 directory with 2 updates Bumps the github-actions group with 2 updates in the / directory: [ruby/setup-ruby](https://github.com/ruby/setup-ruby) and [taiki-e/install-action](https://github.com/taiki-e/install-action). Updates `ruby/setup-ruby` from 1.288.0 to 1.289.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/09a7688d3b55cf0e976497ff046b70949eeaccfd...19a43a6a2428d455dbd1b85344698725179c9d8c) Updates `taiki-e/install-action` from 2.68.18 to 2.68.19 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/205eb1d74c6feda89abb1f3a09360601953286c0...385db9cc6bf65d19775b02084a4b698eaca9a4f2) --- updated-dependencies: - dependency-name: ruby/setup-ruby dependency-version: 1.289.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: taiki-e/install-action dependency-version: 2.68.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/annocheck.yml | 2 +- .github/workflows/auto_review_pr.yml | 2 +- .github/workflows/baseruby.yml | 2 +- .github/workflows/bundled_gems.yml | 2 +- .github/workflows/check_dependencies.yml | 2 +- .github/workflows/check_misc.yml | 2 +- .github/workflows/modgc.yml | 2 +- .github/workflows/parse_y.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/spec_guards.yml | 2 +- .github/workflows/sync_default_gems.yml | 2 +- .github/workflows/ubuntu.yml | 2 +- .github/workflows/wasm.yml | 2 +- .github/workflows/windows.yml | 2 +- .github/workflows/yjit-ubuntu.yml | 2 +- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 4 ++-- 17 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/annocheck.yml b/.github/workflows/annocheck.yml index e7351adc0c9d59..ecaca98b8b6b10 100644 --- a/.github/workflows/annocheck.yml +++ b/.github/workflows/annocheck.yml @@ -73,7 +73,7 @@ jobs: builddir: build makeup: true - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/auto_review_pr.yml b/.github/workflows/auto_review_pr.yml index 9c826666483724..1eb931e82ee4fe 100644 --- a/.github/workflows/auto_review_pr.yml +++ b/.github/workflows/auto_review_pr.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/baseruby.yml b/.github/workflows/baseruby.yml index 4563a455fc05b0..223b30e7e86042 100644 --- a/.github/workflows/baseruby.yml +++ b/.github/workflows/baseruby.yml @@ -48,7 +48,7 @@ jobs: - ruby-3.3 steps: - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/bundled_gems.yml b/.github/workflows/bundled_gems.yml index 5fffa0d6954d13..7254aa669e0a29 100644 --- a/.github/workflows/bundled_gems.yml +++ b/.github/workflows/bundled_gems.yml @@ -38,7 +38,7 @@ jobs: with: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: 4.0 diff --git a/.github/workflows/check_dependencies.yml b/.github/workflows/check_dependencies.yml index 1919e4f7bf2365..29ddfa80d697f3 100644 --- a/.github/workflows/check_dependencies.yml +++ b/.github/workflows/check_dependencies.yml @@ -42,7 +42,7 @@ jobs: - uses: ./.github/actions/setup/directories - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/check_misc.yml b/.github/workflows/check_misc.yml index 6569c4f726fcfa..32cbcb53c8a7cf 100644 --- a/.github/workflows/check_misc.yml +++ b/.github/workflows/check_misc.yml @@ -23,7 +23,7 @@ jobs: token: ${{ (github.repository == 'ruby/ruby' && !startsWith(github.event_name, 'pull')) && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: head diff --git a/.github/workflows/modgc.yml b/.github/workflows/modgc.yml index fcde133c802a89..435efaea0b80af 100644 --- a/.github/workflows/modgc.yml +++ b/.github/workflows/modgc.yml @@ -62,7 +62,7 @@ jobs: uses: ./.github/actions/setup/ubuntu if: ${{ contains(matrix.os, 'ubuntu') }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/parse_y.yml b/.github/workflows/parse_y.yml index 13ca6ad5d3fba1..ec34cc742c2f4c 100644 --- a/.github/workflows/parse_y.yml +++ b/.github/workflows/parse_y.yml @@ -59,7 +59,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7db5d2f1891f0b..23c6689042e7f1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,7 +19,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: 3.3.4 diff --git a/.github/workflows/spec_guards.yml b/.github/workflows/spec_guards.yml index 029f4a6bd43962..0314269a32594c 100644 --- a/.github/workflows/spec_guards.yml +++ b/.github/workflows/spec_guards.yml @@ -49,7 +49,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: ${{ matrix.ruby }} bundler: none diff --git a/.github/workflows/sync_default_gems.yml b/.github/workflows/sync_default_gems.yml index 8355159e249e57..3ffe0cc1a5a1f2 100644 --- a/.github/workflows/sync_default_gems.yml +++ b/.github/workflows/sync_default_gems.yml @@ -36,7 +36,7 @@ jobs: with: token: ${{ github.repository == 'ruby/ruby' && secrets.MATZBOT_AUTO_UPDATE_TOKEN || secrets.GITHUB_TOKEN }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.4' bundler: none diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 0de982cbe4f369..d0491f4482d278 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -70,7 +70,7 @@ jobs: with: arch: ${{ matrix.arch }} - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index b9f02735dd8992..0196fa51cf5616 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -99,7 +99,7 @@ jobs: run: | echo "WASI_SDK_PATH=/opt/wasi-sdk" >> $GITHUB_ENV - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 1b9be96ab7b189..ae01cf3ebb5d56 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -59,7 +59,7 @@ jobs: - run: md build working-directory: - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: # windows-11-arm has only 3.4.1, 3.4.2, 3.4.3, head ruby-version: ${{ !endsWith(matrix.os, 'arm') && '3.1' || '3.4' }} diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index 1ff0b1deb3b765..0f40de798ceb3d 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -133,7 +133,7 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index baf1b1bc0a6cd6..12b4fc08412e31 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 + - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 6c529ed6bc40c4..20e6b456756c2e 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -114,12 +114,12 @@ jobs: - uses: ./.github/actions/setup/ubuntu - - uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0 + - uses: ruby/setup-ruby@19a43a6a2428d455dbd1b85344698725179c9d8c # v1.289.0 with: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 + - uses: taiki-e/install-action@385db9cc6bf65d19775b02084a4b698eaca9a4f2 # v2.68.19 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} From 0201e926b05cfb559346c8b6b8bc1d1dbff44aad Mon Sep 17 00:00:00 2001 From: git Date: Thu, 5 Mar 2026 02:42:29 +0000 Subject: [PATCH 6/7] [DOC] Update bundled gems list at 97e27d7f30a0df4b0b67a619d5df4f --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 353243add1f3f3..7a73e16f9f54cb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -77,6 +77,7 @@ releases. * 3.1.6 to [v3.1.7][strscan-v3.1.7] * syntax_suggest 2.0.3 * zlib 3.2.3 + * 3.2.2 to [v3.2.3][zlib-v3.2.3] ### The following bundled gems are updated. @@ -137,6 +138,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [prism-v1.9.0]: https://github.com/ruby/prism/releases/tag/v1.9.0 [resolv-v0.7.1]: https://github.com/ruby/resolv/releases/tag/v0.7.1 [strscan-v3.1.7]: https://github.com/ruby/strscan/releases/tag/v3.1.7 +[zlib-v3.2.3]: https://github.com/ruby/zlib/releases/tag/v3.2.3 [test-unit-3.7.4]: https://github.com/test-unit/test-unit/releases/tag/3.7.4 [test-unit-3.7.5]: https://github.com/test-unit/test-unit/releases/tag/3.7.5 [test-unit-3.7.6]: https://github.com/test-unit/test-unit/releases/tag/3.7.6 From 8a87cebd1874f8f9f68af8928191ee3f0d97bb28 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Wed, 4 Mar 2026 21:55:54 -0600 Subject: [PATCH 7/7] [DOC] Add note about link fragments (#16304) --- doc/contributing/documentation_guide.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index 51b480103c70ac..9945ab57fbf28a 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -288,6 +288,30 @@ The link should lead to a target in https://docs.ruby-lang.org/en/master/. Also use a full URL-based link for a link to an off-site document. +#### Fragments + +In general, a link that includes a [fragment][fragment] +must cite the exact identifier on the target page; +otherwise, the browser finds no suitable identifier, +and does not scroll to the desired part of the page. + +However, certain pages on `github.com` and `github.io` +support "fuzzy" identifier matching, so that URL +https://github.com/rdp/ruby_tutorials_core/wiki/Ruby-Talk-FAQ#-why-are-rubys-floats-imprecise, +(whose fragment is `-why-are-rubys-floats-imprecise`) +scrolls to heading "Why are ruby’s floats imprecise?" +even though the identifier there actually is the longer +`#user-content--why-are-rubys-floats-imprecise`. + +Ruby documentation should avoid using these shortened fragments, for two reasons: + +- The GitHub pages that do this implement it using Javascript; + if the user's browser has Javascript disabled + (which some employers actually require), + the shortened fragment is ineffective and the desired scrolling does not occur. +- A program that checks links in Ruby documentation will find no suitable identifier, + and therefore will report the fragment as not found. + ### Variable Names The name of a variable (as specified in its call-seq) should be marked up as @@ -617,6 +641,7 @@ best to use a separate paragraph for each case you are discussing. [bold text]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#bold [call-seq]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#directive-for-specifying-rdoc-source-format [code blocks]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#code-blocks +[fragment]: https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Fragment [headings]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#headings [irb]: https://ruby.github.io/irb/index.html [links]: https://ruby.github.io/rdoc/doc/markup_reference/rdoc_rdoc.html#links