From 83713db7f1d0c927984350954ec6b8e72662482a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 27 Jan 2026 11:18:31 +0100 Subject: [PATCH 1/4] gc.c: Fix `rb_gc_obj_needs_cleanup_p` - T_BIGNUM may have fields via `#object_id`. - The T_DATA logic was inversed. If `dfree` is unset we don't need cleanup. --- gc.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/gc.c b/gc.c index f1c7f834d0f70c..935a9f5d4bdaaa 100644 --- a/gc.c +++ b/gc.c @@ -1278,15 +1278,6 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) } case T_DATA: - if (flags & RUBY_TYPED_FL_IS_TYPED_DATA) { - uintptr_t type = (uintptr_t)RTYPEDDATA(obj)->type; - if (type & TYPED_DATA_EMBEDDED) { - RUBY_DATA_FUNC dfree = ((const rb_data_type_t *)(type & TYPED_DATA_PTR_MASK))->function.dfree; - return (dfree == RUBY_NEVER_FREE || dfree == RUBY_TYPED_DEFAULT_FREE); - } - } - return true; - case T_OBJECT: case T_STRING: case T_ARRAY: @@ -1298,7 +1289,13 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case T_COMPLEX: break; - default: + case T_FILE: + case T_SYMBOL: + case T_CLASS: + case T_ICLASS: + case T_MODULE: + case T_REGEXP: + case T_MATCH: return true; } @@ -1310,6 +1307,18 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) if (flags & ROBJECT_HEAP) return true; return false; + case T_DATA: + if (flags & RUBY_TYPED_FL_IS_TYPED_DATA) { + uintptr_t type = (uintptr_t)RTYPEDDATA(obj)->type; + if (type & TYPED_DATA_EMBEDDED) { + RUBY_DATA_FUNC dfree = ((const rb_data_type_t *)(type & TYPED_DATA_PTR_MASK))->function.dfree; + if (dfree == RUBY_NEVER_FREE || dfree == RUBY_TYPED_DEFAULT_FREE) { + return false; + } + } + } + return true; + case T_STRING: if (flags & (RSTRING_NOEMBED | RSTRING_FSTR)) return true; return rb_shape_has_fields(shape_id); @@ -1324,7 +1333,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj) case T_BIGNUM: if (!(flags & BIGNUM_EMBED_FLAG)) return true; - return false; + return rb_shape_has_fields(shape_id); case T_STRUCT: if (!(flags & RSTRUCT_EMBED_LEN_MASK)) return true; From fa3e3d1090b2f843735c467aec31dfe7c34581cd Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 27 Jan 2026 23:38:38 +0900 Subject: [PATCH 2/4] Ignore EOL code changes [ci skip] --- .git-blame-ignore-revs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index d98646febf69c1..bc5d291065c335 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -39,3 +39,7 @@ d4e24021d39e1f80f0055b55d91f8d5f22e15084 e90282be7ba1bc8e3119f6e1a2c80356ceb3f80a 26a9e0b4e31f7b5a9cbd755e0a15823a8fa51bae 2f53985da9ee593fe524d408256835667938c7d7 + +# Win32: EOL code of batch files +23f9a0d655c4d405bb2397a147a1523436205486 +b839989fd22fef85e2af19de1bc83aa72a5b22bd From 5d769228c1055524205437860beb1fc2de2d11a0 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 26 Jan 2026 17:28:31 +0100 Subject: [PATCH 3/4] [ruby/prism] Remove `Prism.lex_ripper` Since `on_sp` is emitted, it doesn't do a whole lot anymore. This leaves one incompatibility for code like `"x#$%"` Ripper confuses this for bare interpolation with a global, but `$%` is not a valid global name. Still, it emits two string tokens in such a case. It doesn't make sense for prism to work around this bug, so the affected files are added as excludes. Since the only usage of this method makes sense for testing in prism itself, the method is removed instead of deprecated. https://github.com/ruby/prism/commit/31be379f98 --- lib/prism.rb | 11 --------- lib/prism/lex_ripper.rb | 55 ----------------------------------------- lib/prism/prism.gemspec | 1 - test/prism/bom_test.rb | 3 ++- test/prism/lex_test.rb | 3 ++- 5 files changed, 4 insertions(+), 69 deletions(-) delete mode 100644 lib/prism/lex_ripper.rb diff --git a/lib/prism.rb b/lib/prism.rb index dab3420377214f..781bd4bb0115db 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -20,7 +20,6 @@ module Prism autoload :DSL, "prism/dsl" autoload :InspectVisitor, "prism/inspect_visitor" autoload :LexCompat, "prism/lex_compat" - autoload :LexRipper, "prism/lex_ripper" autoload :MutationCompiler, "prism/mutation_compiler" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" @@ -35,7 +34,6 @@ module Prism # private here. private_constant :LexCompat - private_constant :LexRipper # Raised when requested to parse as the currently running Ruby version but Prism has no support for it. class CurrentVersionError < ArgumentError @@ -68,15 +66,6 @@ def self.lex_compat(source, **options) LexCompat.new(source, **options).result # steep:ignore end - # :call-seq: - # Prism::lex_ripper(source) -> Array - # - # This wraps the result of Ripper.lex. It produces almost exactly the - # same tokens. Raises SyntaxError if the syntax in source is invalid. - def self.lex_ripper(source) - LexRipper.new(source).result # steep:ignore - end - # :call-seq: # Prism::load(source, serialized, freeze) -> ParseResult # diff --git a/lib/prism/lex_ripper.rb b/lib/prism/lex_ripper.rb deleted file mode 100644 index f069e50ba9aa77..00000000000000 --- a/lib/prism/lex_ripper.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true -# :markup: markdown - -require "ripper" - -module Prism - # This is a class that wraps the Ripper lexer to produce almost exactly the - # same tokens. - class LexRipper # :nodoc: - attr_reader :source - - def initialize(source) - @source = source - end - - def result - previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] - results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] - - lex(source).each do |token| - case token[1] - when :on_tstring_content - if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) - previous[2] << token[2] - else - results << token - previous = token - end - else - results << token - previous = token - end - end - - results - end - - private - - if Ripper.method(:lex).parameters.assoc(:keyrest) - def lex(source) - Ripper.lex(source, raise_errors: true) - end - else - def lex(source) - ripper = Ripper::Lexer.new(source) - ripper.lex.tap do |result| - raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? - end - end - end - end - - private_constant :LexRipper -end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 283c7b04aa95e6..8c9b140f0e342b 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -77,7 +77,6 @@ Gem::Specification.new do |spec| "lib/prism/ffi.rb", "lib/prism/inspect_visitor.rb", "lib/prism/lex_compat.rb", - "lib/prism/lex_ripper.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node_ext.rb", "lib/prism/node.rb", diff --git a/test/prism/bom_test.rb b/test/prism/bom_test.rb index 890bc4b36c3ac4..0fa00ae4e844b6 100644 --- a/test/prism/bom_test.rb +++ b/test/prism/bom_test.rb @@ -5,6 +5,7 @@ return if RUBY_ENGINE != "ruby" require_relative "test_helper" +require "ripper" module Prism class BOMTest < TestCase @@ -53,7 +54,7 @@ def test_string def assert_bom(source) bommed = "\xEF\xBB\xBF#{source}" - assert_equal Prism.lex_ripper(bommed), Prism.lex_compat(bommed).value + assert_equal Ripper.lex(bommed), Prism.lex_compat(bommed).value end end end diff --git a/test/prism/lex_test.rb b/test/prism/lex_test.rb index ea4606d2fb6251..9a9f203c280d98 100644 --- a/test/prism/lex_test.rb +++ b/test/prism/lex_test.rb @@ -3,6 +3,7 @@ return if !(RUBY_ENGINE == "ruby" && RUBY_VERSION >= "3.2.0") require_relative "test_helper" +require "ripper" module Prism class LexTest < TestCase @@ -49,7 +50,7 @@ def test_parse_lex_file if RUBY_VERSION >= "3.3" def test_lex_compare prism = Prism.lex_compat(File.read(__FILE__), version: "current").value - ripper = Prism.lex_ripper(File.read(__FILE__)) + ripper = Ripper.lex(File.read(__FILE__)) assert_equal(ripper, prism) end end From 52e71ad4b766d57236e66ebbc419a4447fb0b491 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Tue, 27 Jan 2026 12:36:55 -0500 Subject: [PATCH 4/4] Fix test for mutex starvation as well as small fix in thread_sync.c (#15982) Don't reset `th->running_time_us` when unlocking from `mutex_free` or force unlocking during thread destruction. Follow-up to 994257ab06072d. --- test/ruby/test_thread.rb | 12 +++++++----- thread_sync.c | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/ruby/test_thread.rb b/test/ruby/test_thread.rb index 60e3aa772a8642..47a8e94c07c008 100644 --- a/test/ruby/test_thread.rb +++ b/test/ruby/test_thread.rb @@ -1667,22 +1667,24 @@ def test_mn_threads_sub_millisecond_sleep # [Bug #21840] def test_mutex_owner_doesnt_starve_waiters - assert_ruby_status([], "#{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; + require "tempfile" + temp = Tempfile.new("temp") m = Mutex.new - fib = lambda { |n| + def fib(n) return n if n <= 1 fib(n - 1) + fib(n - 2) - } + end t1_running = false - t1 = Thread.new do + Thread.new do t1_running = true loop do fib(20) m.synchronize do - File.open(__FILE__) { } # reset timeslice due to blocking operation + File.open(temp.path) { } # reset timeslice due to blocking operation end end end diff --git a/thread_sync.c b/thread_sync.c index 2963b6db73b123..8b86c903809c8d 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -466,7 +466,7 @@ rb_mutex_unlock_th(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) struct sync_waiter *cur = 0, *next; - if (mutex->wait_waking) { + if (mutex->wait_waking && ec_serial) { uint32_t saved = mutex->saved_running_time_us; if (th->running_time_us < saved) { th->running_time_us = saved;