From 3aa34522c1b13dfcb7dc8ba459eed90f3d886632 Mon Sep 17 00:00:00 2001 From: Oskar Eichler <62393985+OskarEichler@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:20:22 +0700 Subject: [PATCH 1/4] [ruby/timeout] Exclude the gemspec from packaged files https://github.com/ruby/timeout/commit/72a3743ec4 --- lib/timeout.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/timeout.gemspec b/lib/timeout.gemspec index 5449494e071b2f..03e71ee430afef 100644 --- a/lib/timeout.gemspec +++ b/lib/timeout.gemspec @@ -26,7 +26,7 @@ Gem::Specification.new do |spec| spec.files = Dir.chdir(__dir__) do `git ls-files -z`.split("\x0").reject do |f| - (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) + (f == File.basename(__FILE__)) || f.match(%r{\A(?:(?:bin|test|spec|features|rakelib)/|\.(?:git|travis|circleci)|appveyor|Rakefile)}) end end spec.require_paths = ["lib"] From fdd520806dc59ad5592312f40326c3a8cd63a79c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 27 Aug 2026 19:31:32 +0900 Subject: [PATCH 2/4] Fix prepend ancestry order in user boxes ensure_origin read the m_tbl before the first write created the box-local classext, so the origin ICLASS captured the prime m_tbl while the subclass ICLASSes referred the box-local copy made by the CoW. rb_prepend_module then missed the shared m_tbl check and inserted the prepended module after the target module instead of before it. Co-Authored-By: Claude Fable 5 --- class.c | 4 ++++ test/ruby/test_box.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/class.c b/class.c index da22fd3583006a..f3ea869573a6ca 100644 --- a/class.c +++ b/class.c @@ -1843,6 +1843,10 @@ ensure_origin(VALUE klass) { VALUE origin = RCLASS_ORIGIN(klass); if (origin == klass) { + /* Create the box-local classext before reading m_tbl, so that the + * origin shares the m_tbl with the box-local iclasses of klass, + * as rb_prepend_module relies on that identity. */ + rb_class_ensure_writable(klass); origin = class_alloc(T_ICLASS, klass); RCLASS_SET_M_TBL(origin, RCLASS_M_TBL(klass)); rb_class_set_super(origin, RCLASS_SUPER(klass)); diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 9ee932b442a632..0c48ceec55e856 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -372,6 +372,21 @@ def test_descendants_follow_ancestors_of_the_current_box assert_include @box::BoxedString.ancestors, String assert_include String.descendants, @box::BoxedString end + + def test_prepend_to_builtin_module_in_box + # Use --disable-gems to keep Kernel untouched in the new box, so that + # the prepend below is the first copy-on-write of Kernel's classext + assert_separately([ENV_ENABLE_BOX, '--disable-gems'], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + box = Ruby::Box.new + box.eval('module BoxDecor; def itself; :decorated; end; end; Kernel.prepend(BoxDecor)') + ancestors = box.eval('Object.ancestors') + assert_operator ancestors.index(box::BoxDecor), :<, ancestors.index(Kernel) + assert_equal :decorated, box.eval('Object.new.itself') + assert_not_include Object.ancestors, box::BoxDecor + assert_equal 42, 42.itself + end; + end end class TestBoxDescendantsMain From 13c99ffab90a5966fb6f7c3afbfb9aa4e1243214 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 27 Aug 2026 19:05:22 +0900 Subject: [PATCH 3/4] Fix autoload's require dispatch under Ruby::Box With RUBY_BOX=1, firing an autoload invoked Ruby::Box#require on the registered box, bypassing any Kernel#require decoration (RubyGems, Zeitwerk, ...) prepended in that box. Zeitwerk's implicit namespaces rely on the decorated require, so a plain Rails app failed with LoadError on the first request. Call require on the box's top self in a frame running in that box instead, which dispatches the decorated method and still loads the feature into the box that registered the autoload. --- test/ruby/test_box.rb | 52 +++++++++++++++++++++++++++++++++++++++++++ variable.c | 33 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/test/ruby/test_box.rb b/test/ruby/test_box.rb index 0c48ceec55e856..43f13494de3782 100644 --- a/test/ruby/test_box.rb +++ b/test/ruby/test_box.rb @@ -203,6 +203,58 @@ def test_autoload_in_box assert_raise(NameError) { BOX_B } end + def test_autoload_dispatches_prepended_require + # Autoload must go through the `Kernel#require` decorations (Zeitwerk, RubyGems, etc.) + # of the box that registered the autoload, not `Ruby::Box#require` directly. + assert_separately([ENV_ENABLE_BOX], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + FEATURE = "/nonexistent/virtual_feature" + module Decor + def require(path) + if path == FEATURE + Object.const_set(:AutoloadedFromDecorator, Module.new) + return true + end + super + end + end + Kernel.prepend(Decor) + Object.autoload(:AutoloadedFromDecorator, FEATURE) + assert_kind_of Module, AutoloadedFromDecorator + end; + end + + def test_autoload_dispatches_prepended_require_of_the_registered_box + # Even when the autoload is triggered from outside, it must be dispatched to the + # (decorated) `Kernel#require` of the box that registered the autoload. + # --enable=gems because Kernel.prepend in a box without RubyGems has a separate + # ancestry ordering problem, and assert_separately runs with --disable=gems. + assert_in_out_err([ENV_ENABLE_BOX, "--enable=gems"], "#{<<-"begin;"}\n#{<<-'end;'}") do |output, error| + begin; + box = Ruby::Box.new + box.eval(<<~RUBY) + FEATURE = "/nonexistent/box_virtual_feature" + module BoxDecor + def require(path) + if path == FEATURE + Holder.const_set(:Virtual, "decorated in \#{Ruby::Box.current.inspect}") + return true + end + super + end + end + Kernel.prepend(BoxDecor) + module Holder + autoload :Virtual, FEATURE + end + RUBY + puts box::Holder::Virtual + end; + assert_equal 1, output.size + assert_match(/\Adecorated in #autoload_const; @@ -3176,9 +3182,6 @@ autoload_feature_require(VALUE _arguments) // We save this for later use in autoload_apply_constants: arguments->autoload_data = rb_check_typeddata(autoload_const->autoload_data_value, &autoload_data_type); - if (rb_box_available() && BOX_OBJ_P(autoload_box_value)) - receiver = autoload_box_value; - /* * Clear the global cc cache table because the require method can be different from the current * box's one and it may cause inconsistent cc-cme states. @@ -3187,7 +3190,25 @@ autoload_feature_require(VALUE _arguments) */ rb_gccct_clear_table(); - VALUE result = rb_funcall(receiver, rb_intern("require"), 1, arguments->autoload_data->feature); + VALUE feature = arguments->autoload_data->feature; + rb_box_t *box = NULL; + if (rb_box_available() && BOX_OBJ_P(autoload_box_value)) { + box = rb_get_box_t(autoload_box_value); + } + + VALUE result; + if (box && box->top_self) { + /* + * Call `require` on the top self of the box that registered the autoload, in a frame + * running in that box, so that `Kernel#require` decorations in the box (RubyGems, + * Zeitwerk, etc.) are dispatched and the feature is loaded into that box. + */ + result = rb_vm_call_cfunc_in_box(box->top_self, autoload_feature_require_in_box, + box->top_self, feature, feature, box); + } + else { + result = rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, feature); + } if (RTEST(result)) { return rb_mutex_synchronize(autoload_mutex, autoload_apply_constants, _arguments); From 2fed55ed99ff05f317e7e12755215a65e5f31474 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 31 Aug 2026 08:37:50 +0900 Subject: [PATCH 4/4] [ruby/rubygems] Load RDoc before the test sandbox in TestGemRDoc RDoc 8 depends on rbs, and that dependency stays unresolved when several rbs versions are installed. It is then resolved on the first require of rbs, which happens inside Gem::RDoc#setup, after Gem::TestCase#setup has pointed GEM_HOME at an empty temporary directory, so it raises LoadError. https://github.com/ruby/rubygems/commit/e794ab7e5f Co-Authored-By: Claude Opus 5 --- test/rubygems/test_gem_rdoc.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/rubygems/test_gem_rdoc.rb b/test/rubygems/test_gem_rdoc.rb index 9ecbb7d8c39b38..da061c74486765 100644 --- a/test/rubygems/test_gem_rdoc.rb +++ b/test/rubygems/test_gem_rdoc.rb @@ -4,6 +4,11 @@ require_relative "helper" require "rubygems/rdoc" +# RDoc resolves its own dependencies lazily, on require. Load them here, while +# the real gem paths are still in effect, because Gem::TestCase#setup points +# GEM_HOME at an empty temporary directory where they cannot be found. +Gem::RDoc.load_rdoc if defined?(Gem::RDoc) + class TestGemRDoc < Gem::TestCase def setup super