From 8d438678023aa453717954bb519308b5c3eec0fe Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 14 Oct 2025 17:30:52 -0700 Subject: [PATCH 1/4] [rubygems/rubygems] remove some memoization I don't think these methods are hotspots, and since gem specifications are sometimes serialized to yaml / marshal, I think we should remove as many instance variables as possible https://github.com/rubygems/rubygems/commit/40490d918b --- lib/rubygems/specification.rb | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 1d351f8aff9746..ea7b58f20e23a9 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -488,8 +488,6 @@ def platform=(platform) end @platform = @new_platform.to_s - - invalidate_memoized_attributes end ## @@ -1620,14 +1618,14 @@ def build_info_file # spec's cached gem. def cache_dir - @cache_dir ||= File.join base_dir, "cache" + File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file - @cache_file ||= File.join cache_dir, "#{full_name}.gem" + File.join cache_dir, "#{full_name}.gem" end ## @@ -1903,10 +1901,6 @@ def for_cache spec end - def full_name - @full_name ||= super - end - ## # Work around old bundler versions removing my methods # Can be removed once RubyGems can no longer install Bundler 2.5 @@ -2044,17 +2038,6 @@ def base_dir end end - ## - # Expire memoized instance variables that can incorrectly generate, replace - # or miss files due changes in certain attributes used to compute them. - - def invalidate_memoized_attributes - @full_name = nil - @cache_file = nil - end - - private :invalidate_memoized_attributes - def inspect # :nodoc: if $DEBUG super @@ -2093,8 +2076,6 @@ def licenses def internal_init # :nodoc: super @bin_dir = nil - @cache_dir = nil - @cache_file = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @@ -2606,9 +2587,6 @@ def validate_permissions def version=(version) @version = Gem::Version.create(version) - return if @version.nil? - - invalidate_memoized_attributes end def stubbed? From 5bda42e4dec9106fb310c30178c1283ff92ebfa2 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Tue, 14 Oct 2025 21:45:10 -0400 Subject: [PATCH 2/4] ZJIT: Include GC object dump when seeing dead objects Strictly more info than just the builtin_type from `assert_ne!`. Old: assertion `left != right` failed: ZJIT should only see live objects left: 0 right: 0 New: ZJIT saw a dead object. T_type=0, out-of-heap:0x0000000110d4bb40 Also, the new `VALUE::obj_info` is more flexible for print debugging than the dump_info() it replaces. It now allows you to use it as part of a `format!` string instead of always printing to stderr for you. --- zjit/bindgen/src/main.rs | 2 +- zjit/src/cruby.rs | 32 ++++++++++++++++++++++++++------ zjit/src/cruby_bindings.inc.rs | 6 +++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index b40986c0f6e154..975a2d91570613 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -82,7 +82,7 @@ fn main() { .allowlist_type("ruby_rstring_flags") // This function prints info about a value and is useful for debugging - .allowlist_function("rb_obj_info_dump") + .allowlist_function("rb_raw_obj_info") .allowlist_function("ruby_init") .allowlist_function("ruby_init_stack") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1f514787f113d0..1a2dce03eda316 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -90,7 +90,7 @@ use std::convert::From; use std::ffi::{c_void, CString, CStr}; -use std::fmt::{Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::os::raw::{c_char, c_int, c_uint}; use std::panic::{catch_unwind, UnwindSafe}; @@ -400,10 +400,27 @@ pub enum ClassRelationship { NoRelation, } +/// A print adapator for debug info about a [VALUE]. Includes info +/// the GC knows about the handle. Example: `println!("{}", value.obj_info());`. +pub struct ObjInfoPrinter(VALUE); + +impl Display for ObjInfoPrinter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use std::mem::MaybeUninit; + const BUFFER_SIZE: usize = 0x100; + let mut buffer: MaybeUninit<[c_char; BUFFER_SIZE]> = MaybeUninit::uninit(); + let info = unsafe { + rb_raw_obj_info(buffer.as_mut_ptr().cast(), BUFFER_SIZE, self.0); + CStr::from_ptr(buffer.as_ptr().cast()).to_string_lossy() + }; + write!(f, "{info}") + } +} + impl VALUE { - /// Dump info about the value to the console similarly to rp(VALUE) - pub fn dump_info(self) { - unsafe { rb_obj_info_dump(self) } + /// Get a printer for raw debug info from `rb_obj_info()` about the value. + pub fn obj_info(self) -> ObjInfoPrinter { + ObjInfoPrinter(self) } /// Return whether the value is truthy or falsy in Ruby -- only nil and false are falsy. @@ -507,8 +524,11 @@ impl VALUE { pub fn class_of(self) -> VALUE { if !self.special_const_p() { let builtin_type = self.builtin_type(); - assert_ne!(builtin_type, RUBY_T_NONE, "ZJIT should only see live objects"); - assert_ne!(builtin_type, RUBY_T_MOVED, "ZJIT should only see live objects"); + assert!( + builtin_type != RUBY_T_NONE && builtin_type != RUBY_T_MOVED, + "ZJIT saw a dead object. T_type={builtin_type}, {}", + self.obj_info() + ); } unsafe { rb_yarv_class_of(self) } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 56b569e064c0b0..f18c0035ee0f67 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -839,7 +839,6 @@ unsafe extern "C" { pub fn rb_ivar_set(obj: VALUE, name: ID, val: VALUE) -> VALUE; pub fn rb_ivar_defined(obj: VALUE, name: ID) -> VALUE; pub fn rb_attr_get(obj: VALUE, name: ID) -> VALUE; - pub fn rb_obj_info_dump(obj: VALUE); pub fn rb_class_allocate_instance(klass: VALUE) -> VALUE; pub fn rb_obj_equal(obj1: VALUE, obj2: VALUE) -> VALUE; pub fn rb_reg_new_ary(ary: VALUE, options: ::std::os::raw::c_int) -> VALUE; @@ -872,6 +871,11 @@ unsafe extern "C" { cfp: *const rb_control_frame_t, ) -> *const rb_callable_method_entry_t; pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; + pub fn rb_raw_obj_info( + buff: *mut ::std::os::raw::c_char, + buff_size: usize, + obj: VALUE, + ) -> *const ::std::os::raw::c_char; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; From 26d1e6947e174b499aae9833d9a4c434fa9a5415 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Oct 2025 09:28:20 +0900 Subject: [PATCH 3/4] [rubygems/rubygems] Replaced Bundler::SharedHelpers.major_deprecation to feature_removed! or feature_deprecated! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/rubygems/rubygems/commit/b1b963b34a Co-authored-by: David Rodríguez <2887858+deivid-rodriguez@users.noreply.github.com> --- lib/bundler/cli.rb | 3 +-- lib/bundler/cli/config.rb | 3 +-- lib/bundler/cli/update.rb | 2 +- lib/bundler/definition.rb | 2 +- lib/bundler/dsl.rb | 8 ++---- lib/bundler/shared_helpers.rb | 19 ++----------- spec/bundler/bundler/shared_helpers_spec.rb | 30 --------------------- 7 files changed, 8 insertions(+), 59 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 62225a352d71fa..9c7c1217fbf8bd 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -447,9 +447,8 @@ def cache D def exec(*args) if ARGV.include?("--no-keep-file-descriptors") - message = "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/exec" diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb index d963679085ebb7..6a77e4a65ea386 100644 --- a/lib/bundler/cli/config.rb +++ b/lib/bundler/cli/config.rb @@ -26,8 +26,7 @@ def base(name = nil, *value) end message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." - removed_message = "Using the `config` command without a subcommand [list, get, set, unset] has been removed. Use `bundle #{new_args.join(" ")}` instead." - SharedHelpers.major_deprecation 4, message, removed_message: removed_message + SharedHelpers.feature_deprecated! message Base.new(options, name, value, self).run end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 4b4ba3c64712be..9cc90acc58536a 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -26,7 +26,7 @@ def run if Bundler.settings[:update_requires_all_flag] raise InvalidOption, "To update everything, pass the `--all` flag." end - SharedHelpers.major_deprecation 4, "Pass --all to `bundle update` to update everything" + SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything" elsif !full_update && options[:all] raise InvalidOption, "Cannot specify --all along with specific options." end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index cc2394fda60f0a..6cae8964d8145a 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -372,7 +372,7 @@ def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or msg = "`Definition#lock` was passed a target file argument. #{suggestion}" - Bundler::SharedHelpers.major_deprecation 2, msg + Bundler::SharedHelpers.feature_removed! msg end write_lock(target_lockfile, preserve_unknown_sections) diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index d98dbd4759e922..998a8134f8e12b 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -483,14 +483,10 @@ def validate_keys(command, opts, valid_keys) def normalize_source(source) case source when :gemcutter, :rubygems, :rubyforge - message = - "The source :#{source} is deprecated because HTTP requests are insecure.\n" \ - "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." removed_message = "The source :#{source} is disallowed because HTTP requests are insecure.\n" \ "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - "http://rubygems.org" + Bundler::SharedHelpers.feature_removed! removed_message when String source else @@ -509,7 +505,7 @@ def check_path_source_safety " gem 'rails'\n" \ " end\n\n" - SharedHelpers.major_deprecation(2, msg.strip) + SharedHelpers.feature_removed! msg.strip end def check_rubygems_source_safety diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 41b7128d36e18e..987a68afd75326 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -125,28 +125,13 @@ def filesystem_access(path, action = :write, &block) raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.") end - def major_deprecation(major_version, message, removed_message: nil, print_caller_location: false) - if print_caller_location - caller_location = caller_locations(2, 2).first - suffix = " (called at #{caller_location.path}:#{caller_location.lineno})" - message += suffix - end - - require_relative "../bundler" - - feature_flag = Bundler.feature_flag + def feature_deprecated!(message) + return unless prints_major_deprecations? - if feature_flag.removed_major?(major_version) - feature_removed!(removed_message || message) - end - - return unless feature_flag.deprecated_major?(major_version) && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end def feature_removed!(message) - require_relative "../bundler" - require_relative "errors" raise RemovedError, "[REMOVED] #{message}" end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 5b3a9c17a7e8d5..0aacb93c16e2a9 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -515,34 +515,4 @@ end end end - - describe "#major_deprecation" do - before { allow(Bundler).to receive(:feature_flag).and_return(Bundler::FeatureFlag.new(37)) } - before { allow(Bundler.ui).to receive(:warn) } - - it "prints and raises nothing below the deprecated major version" do - subject.major_deprecation(38, "Message") - subject.major_deprecation(39, "Message", removed_message: "Removal", print_caller_location: true) - expect(Bundler.ui).not_to have_received(:warn) - end - - it "prints but does not raise _at_ the deprecated major version" do - subject.major_deprecation(37, "Message") - subject.major_deprecation(37, "Message", removed_message: "Removal") - expect(Bundler.ui).to have_received(:warn).with("[DEPRECATED] Message").twice - - subject.major_deprecation(37, "Message", print_caller_location: true) - expect(Bundler.ui).to have_received(:warn). - with(a_string_matching(/^\[DEPRECATED\] Message \(called at .*:\d+\)$/)) - end - - it "raises the appropriate errors when _past_ the deprecated major version" do - expect { subject.major_deprecation(36, "Message") }. - to raise_error(Bundler::RemovedError, "[REMOVED] Message") - expect { subject.major_deprecation(36, "Message", removed_message: "Removal") }. - to raise_error(Bundler::RemovedError, "[REMOVED] Removal") - expect { subject.major_deprecation(35, "Message", removed_message: "Removal", print_caller_location: true) }. - to raise_error(Bundler::RemovedError, "[REMOVED] Removal") - end - end end From 1142abb1de10d8c47278d09a90d7c0cc713f59af Mon Sep 17 00:00:00 2001 From: Marino Bonetti Date: Wed, 15 Oct 2025 06:58:24 +0200 Subject: [PATCH 4/4] [DOC] Update making_changes_to_stdlibs.md mirror Example CSV is no more part of the standard lib, but the documentation was not updated (the example link was broken for the master branch) Selected ERB that has the dedicated directory, like CSV. --- doc/contributing/making_changes_to_stdlibs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/making_changes_to_stdlibs.md b/doc/contributing/making_changes_to_stdlibs.md index 2156a61e3948a3..2ceb2e60756714 100644 --- a/doc/contributing/making_changes_to_stdlibs.md +++ b/doc/contributing/making_changes_to_stdlibs.md @@ -4,7 +4,7 @@ Everything in the [lib](https://github.com/ruby/ruby/tree/master/lib) directory If you'd like to make contributions to standard libraries, do so in the standalone repositories, and the changes will be automatically mirrored into the Ruby repository. -For example, CSV lives in [a separate repository](https://github.com/ruby/csv) and is mirrored into [Ruby](https://github.com/ruby/ruby/tree/master/lib/csv). +For example, ERB lives in [a separate repository](https://github.com/ruby/erb) and is mirrored into [Ruby](https://github.com/ruby/ruby/tree/master/lib/erb). ## Maintainers