Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1b8c6c1
Suppress a sign-compare warning
nobu Mar 9, 2026
59f744c
Suppress format warnings
nobu Mar 9, 2026
9106826
[ruby/rubygems] Add YAML serializer (dump) for Gem objects
hsbt Mar 6, 2026
91110ce
[ruby/rubygems] Add full YAML parser with recursive descent
hsbt Mar 6, 2026
45cb5d0
[ruby/rubygems] Add Gem object reconstruction from parsed YAML
hsbt Mar 6, 2026
a551f4f
[ruby/rubygems] Refactor YAMLSerializer into Parser/Builder/Emitter
hsbt Mar 6, 2026
2c9e4be
[ruby/rubygems] Add use_psych config and make YAMLSerializer default …
hsbt Mar 9, 2026
852e7cf
[ruby/rubygems] Use YAMLSerializer in SafeYAML with Psych fallback
hsbt Mar 9, 2026
9e85f2c
[ruby/rubygems] Use YAMLSerializer in Specification with Psych fallback
hsbt Mar 9, 2026
e954bd2
[ruby/rubygems] Use YAMLSerializer in Package with Psych fallback
hsbt Mar 9, 2026
6167a6c
[ruby/rubygems] Use YAMLSerializer in specification_command with Psyc…
hsbt Mar 9, 2026
2781b19
[ruby/rubygems] Update test helpers for YAMLSerializer
hsbt Mar 9, 2026
6043049
[ruby/rubygems] Update bundler inline spec expectations
hsbt Mar 9, 2026
1cd2cc2
[ruby/rubygems] Use Psych-specific YAML error classes
hsbt Mar 9, 2026
d31f7c7
[ruby/rubygems] Simplify indentation handling in YAML serializer
hsbt Mar 6, 2026
00e054f
[ruby/rubygems] Optimize YAML serializer line handling
hsbt Mar 6, 2026
8a19f69
[ruby/rubygems] Guard against nil next line in YAML serializer
hsbt Mar 6, 2026
fad2934
[ruby/rubygems] Add comprehensive SafeYAML and YAMLSerializer tests
hsbt Mar 6, 2026
88aeabf
[ruby/rubygems] Add YAMLSerializer round-trip tests
hsbt Mar 6, 2026
4cd3726
[ruby/rubygems] Add unit and regression tests for YAML serializer
hsbt Mar 6, 2026
6a92781
[ruby/rubygems] Add Psych stub classes to yaml serializer
hsbt Mar 9, 2026
6425157
[ruby/rubygems] Simplify Psych exception stubs and fallback raises
hsbt Mar 9, 2026
4da2b2d
[ruby/rubygems] Remove redundant SafeYAML.load and update tests
hsbt Mar 9, 2026
1425c52
Parse ISO8601 datetimes without Time.parse
hsbt Mar 9, 2026
364f2fc
Propose myself as maintainer of benchmark
eregon Dec 5, 2025
b5ffaa3
[ruby/timeout] Fix timing-dependent test
eregon Mar 9, 2026
4ce8515
[ruby/timeout] Remove warnings
etiennebarrie Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/maintainers.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes

#### benchmark

* Benoit Daloze ([eregon])
* https://github.com/ruby/benchmark
* https://rubygems.org/gems/benchmark

Expand Down Expand Up @@ -672,6 +673,7 @@ It may needs to make consensus on ruby-core/ruby-dev before making major changes
[earlopain]: https://github.com/earlopain
[eban]: https://github.com/eban
[eileencodes]: https://github.com/eileencodes
[eregon]: https://github.com/eregon
[hasumikin]: https://github.com/hasumikin
[hsbt]: https://github.com/hsbt
[ima1zumi]: https://github.com/ima1zumi
Expand Down
4 changes: 2 additions & 2 deletions gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -8266,7 +8266,7 @@ rb_gc_impl_free(void *objspace_ptr, void *ptr, size_t old_size)
}

if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
rb_bug("buffer %p freed with old_size=%lu, but was allocated with size=%lu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
rb_bug("buffer %p freed with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
}
#endif
ptr = info;
Expand Down Expand Up @@ -8377,7 +8377,7 @@ rb_gc_impl_realloc(void *objspace_ptr, void *ptr, size_t new_size, size_t old_si
ptr = info;
#if VERIFY_FREE_SIZE
if (old_size && (old_size + sizeof(struct malloc_obj_info)) != info->size) {
rb_bug("buffer %p realloced with old_size=%lu, but was allocated with size=%lu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
rb_bug("buffer %p realloced with old_size=%zu, but was allocated with size=%zu", ptr, old_size, info->size - sizeof(struct malloc_obj_info));
}
#endif
old_size = info->size;
Expand Down
18 changes: 16 additions & 2 deletions lib/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -640,16 +640,30 @@ def self.add_to_load_path(*paths)
end

@yaml_loaded = false
@use_psych = nil

##
# Returns true if the Psych YAML parser is enabled via configuration.

def self.use_psych?
@use_psych || false
end

##
# Loads YAML, preferring Psych

def self.load_yaml
return if @yaml_loaded

require "psych"
require_relative "rubygems/psych_tree"
@use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" ||
(defined?(@configuration) && @configuration && !@configuration[:use_psych].nil?)

if @use_psych
require "psych"
require_relative "rubygems/psych_tree"
end

require_relative "rubygems/yaml_serializer"
require_relative "rubygems/safe_yaml"

@yaml_loaded = true
Expand Down
2 changes: 1 addition & 1 deletion lib/rubygems/commands/specification_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def execute
say case options[:format]
when :ruby then s.to_ruby
when :marshal then Marshal.dump s
else s.to_yaml
else Gem.use_psych? ? s.to_yaml : Gem::YAMLSerializer.dump(s)
end

say "\n"
Expand Down
20 changes: 16 additions & 4 deletions lib/rubygems/config_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Gem::ConfigFile
DEFAULT_IPV4_FALLBACK_ENABLED = false
DEFAULT_INSTALL_EXTENSION_IN_LIB = false
DEFAULT_GLOBAL_GEM_CACHE = false
DEFAULT_USE_PSYCH = false

##
# For Ruby packagers to set configuration defaults. Set in
Expand Down Expand Up @@ -161,6 +162,11 @@ class Gem::ConfigFile

attr_accessor :global_gem_cache

##
# Use Psych (C extension YAML parser) instead of the pure Ruby YAMLSerializer.

attr_accessor :use_psych

##
# Path name of directory or file of openssl client certificate, used for remote https connection with client authentication

Expand Down Expand Up @@ -199,6 +205,7 @@ def initialize(args)
@install_extension_in_lib = DEFAULT_INSTALL_EXTENSION_IN_LIB
@ipv4_fallback_enabled = ENV["IPV4_FALLBACK_ENABLED"] == "true" || DEFAULT_IPV4_FALLBACK_ENABLED
@global_gem_cache = ENV["RUBYGEMS_GLOBAL_GEM_CACHE"] == "true" || DEFAULT_GLOBAL_GEM_CACHE
@use_psych = ENV["RUBYGEMS_USE_PSYCH"] == "true" || DEFAULT_USE_PSYCH

operating_system_config = Marshal.load Marshal.dump(OPERATING_SYSTEM_DEFAULTS)
platform_config = Marshal.load Marshal.dump(PLATFORM_DEFAULTS)
Expand All @@ -221,7 +228,7 @@ def initialize(args)
# gemhome and gempath are not working with symbol keys
if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days
concurrent_downloads install_extension_in_lib ipv4_fallback_enabled
global_gem_cache sources
global_gem_cache use_psych sources
disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k)
k.to_sym
else
Expand All @@ -239,6 +246,7 @@ def initialize(args)
@install_extension_in_lib = @hash[:install_extension_in_lib] if @hash.key? :install_extension_in_lib
@ipv4_fallback_enabled = @hash[:ipv4_fallback_enabled] if @hash.key? :ipv4_fallback_enabled
@global_gem_cache = @hash[:global_gem_cache] if @hash.key? :global_gem_cache
@use_psych = @hash[:use_psych] if @hash.key? :use_psych

@home = @hash[:gemhome] if @hash.key? :gemhome
@path = @hash[:gempath] if @hash.key? :gempath
Expand Down Expand Up @@ -378,7 +386,9 @@ def load_file(filename)

begin
config = self.class.load_with_rubygems_config_hash(File.read(filename))
if config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") }
has_invalid_keys = config.keys.any? {|k| k.to_s.gsub(%r{https?:\/\/}, "").include?(": ") }
has_invalid_values = config.values.any? {|v| v.is_a?(String) && v.gsub(%r{https?:\/\/}, "").match?(/\A\S+: /) }
if has_invalid_keys || has_invalid_values
warn "Failed to load #{filename} because it doesn't contain valid YAML hash"
return {}
else
Expand Down Expand Up @@ -563,7 +573,9 @@ def self.dump_with_rubygems_yaml(content)
def self.load_with_rubygems_config_hash(yaml)
require_relative "yaml_serializer"

content = Gem::YAMLSerializer.load(yaml)
content = Gem::YAMLSerializer.load(yaml, permitted_classes: [])
return {} unless content.is_a?(Hash)

deep_transform_config_keys!(content)
end

Expand Down Expand Up @@ -597,7 +609,7 @@ def self.deep_transform_config_keys!(config)
else
v
end
elsif v.empty?
elsif v.respond_to?(:empty?) && v.empty?
nil
elsif v.is_a?(Hash)
deep_transform_config_keys!(v)
Expand Down
6 changes: 5 additions & 1 deletion lib/rubygems/package.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,11 @@ def add_checksums(tar)

tar.add_file_signed "checksums.yaml.gz", 0o444, @signer do |io|
gzip_to io do |gz_io|
Psych.dump checksums_by_algorithm, gz_io
if Gem.use_psych?
Psych.dump checksums_by_algorithm, gz_io
else
gz_io.write Gem::YAMLSerializer.dump(checksums_by_algorithm)
end
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/rubygems/safe_marshal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module SafeMarshal
"Gem::NameTuple" => %w[@name @version @platform],
"Gem::Platform" => %w[@os @cpu @version],
"Psych::PrivateType" => %w[@value @type_id],
"YAML::PrivateType" => %w[@value @type_id],
}.freeze
private_constant :PERMITTED_IVARS

Expand Down
27 changes: 25 additions & 2 deletions lib/rubygems/safe_yaml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,34 @@ def self.aliases_enabled? # :nodoc:
end

def self.safe_load(input)
::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES, permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled)
if Gem.use_psych?
::Psych.safe_load(input, permitted_classes: PERMITTED_CLASSES,
permitted_symbols: PERMITTED_SYMBOLS, aliases: @aliases_enabled)
else
Gem::YAMLSerializer.load(
input,
permitted_classes: PERMITTED_CLASSES,
permitted_symbols: PERMITTED_SYMBOLS,
aliases: aliases_enabled?
)
end
end

def self.load(input)
::Psych.safe_load(input, permitted_classes: [::Symbol])
if Gem.use_psych?
if ::Psych.respond_to?(:unsafe_load)
::Psych.unsafe_load(input)
else
::Psych.load(input)
end
else
Gem::YAMLSerializer.load(
input,
permitted_classes: PERMITTED_CLASSES,
permitted_symbols: PERMITTED_SYMBOLS,
aliases: aliases_enabled?
)
end
end
end
end
36 changes: 20 additions & 16 deletions lib/rubygems/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1275,7 +1275,7 @@ def self._load(str)
raise unless message.include?("YAML::")

unless Object.const_defined?(:YAML)
Object.const_set "YAML", Psych
Object.const_set "YAML", Module.new
yaml_set = true
end

Expand All @@ -1284,7 +1284,7 @@ def self._load(str)

YAML::Syck.const_set "DefaultKey", Class.new if message.include?("YAML::Syck::DefaultKey") && !YAML::Syck.const_defined?(:DefaultKey)
elsif message.include?("YAML::PrivateType") && !YAML.const_defined?(:PrivateType)
YAML.const_set "PrivateType", Class.new
YAML.const_set "PrivateType", Class.new { attr_accessor :type_id, :value }
end

retry_count += 1
Expand Down Expand Up @@ -2455,24 +2455,28 @@ def to_spec
def to_yaml(opts = {}) # :nodoc:
Gem.load_yaml

# Because the user can switch the YAML engine behind our
# back, we have to check again here to make sure that our
# psych code was properly loaded, and load it if not.
unless Gem.const_defined?(:NoAliasYAMLTree)
require_relative "psych_tree"
end
if Gem.use_psych?
# Because the user can switch the YAML engine behind our
# back, we have to check again here to make sure that our
# psych code was properly loaded, and load it if not.
unless Gem.const_defined?(:NoAliasYAMLTree)
require_relative "psych_tree"
end

builder = Gem::NoAliasYAMLTree.create
builder << self
ast = builder.tree
builder = Gem::NoAliasYAMLTree.create
builder << self
ast = builder.tree

require "stringio"
io = StringIO.new
io.set_encoding Encoding::UTF_8
require "stringio"
io = StringIO.new
io.set_encoding Encoding::UTF_8

Psych::Visitors::Emitter.new(io).accept(ast)
Psych::Visitors::Emitter.new(io).accept(ast)

io.string.gsub(/ !!null \n/, " \n")
io.string.gsub(/ !!null \n/, " \n")
else
Gem::YAMLSerializer.dump(self)
end
end

##
Expand Down
Loading