From db44088c2a92040879386aa5f268db4c858e4e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 12 Jan 2024 14:53:40 +0100 Subject: [PATCH] [rubygems/rubygems] Fix activation conflicts when circularly requiring a gem If a gem is required circular, and there are unresolved specs depending on it, we may end up in an activation conflict. The solution is to not try to activate unresolved gems when requiring a default gem, regardless of it having already been activated or not. https://github.com/rubygems/rubygems/commit/3b2b8f4e3e --- lib/rubygems.rb | 7 +++++ lib/rubygems/core_ext/kernel_require.rb | 8 +++-- test/rubygems/test_require.rb | 39 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 47b1ce69d2..ad7ab10756 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -1216,6 +1216,13 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # Find a Gem::Specification of default gem from +path+ + def find_default_spec(path) + @path_to_default_spec_map[path] + end + + ## + # Find an unresolved Gem::Specification of default gem from +path+ + def find_unresolved_default_spec(path) default_spec = @path_to_default_spec_map[path] default_spec if default_spec && loaded_specs[default_spec.name] != default_spec diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 50e3ea89b4..073966b696 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -42,7 +42,11 @@ module Kernel # If +path+ belongs to a default gem, we activate it and then go straight # to normal require - if spec = Gem.find_unresolved_default_spec(path) + if spec = Gem.find_default_spec(path) + name = spec.name + + next if Gem.loaded_specs[name] + # Ensure -I beats a default gem resolved_path = begin rp = nil @@ -60,7 +64,7 @@ module Kernel rp end - Kernel.send(:gem, spec.name, Gem::Requirement.default_prerelease) unless + Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless resolved_path next diff --git a/test/rubygems/test_require.rb b/test/rubygems/test_require.rb index 7f5584ea8a..f595d8e08e 100644 --- a/test/rubygems/test_require.rb +++ b/test/rubygems/test_require.rb @@ -560,6 +560,45 @@ class TestGemRequire < Gem::TestCase assert_require "net/http" end + def test_default_gem_required_circulary_with_unresolved_gems_depending_on_it + net_http_old = util_spec "net-http", "0.1.1", nil, "lib/net/http.rb" + install_gem net_http_old + + net_http_default = new_default_spec "net-http", "0.3.0", nil, "net/http.rb" + net_http_default_path = File.join(@tempdir, "default_gems", "lib", "net/http.rb") + install_default_gems net_http_default + File.write(net_http_default_path, 'require "net/http"') + + faraday_1 = util_spec "faraday", "1", { "net-http" => ">= 0" } + install_gem faraday_1 + + faraday_2 = util_spec "faraday", "2", { "net-http" => ">= 0" } + install_gem faraday_2 + + chef = util_spec "chef", "1", { "faraday" => [">= 1", "< 3"] }, "lib/chef.rb" + install_gem chef + + assert_require "chef" + + out, err = capture_output do + assert_require "net/http" + end + + assert_empty out + + circular_require_warning = false + + err_lines = err.split("\n").reject do |line| + if line.include?("circular require") + circular_require_warning = true + elsif circular_require_warning # ignore backtrace lines for circular require warning + circular_require_warning = line.start_with?(/[\s]/) + end + end + + assert_empty err_lines + end + def loaded_spec_names Gem.loaded_specs.values.map(&:full_name).sort end