From 4e8c65cf3bdef221f58675b727e141b8e3bb7928 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 5 Nov 2024 09:19:03 +0100 Subject: [PATCH] Prevent loading both `json/ext` and `json/pure` Fix: https://github.com/ruby/json/issues/650 Ref: https://github.com/ruby/json/issues/646 `json_pure` currently doesn't work well at all because `json/ext` is a default gem, so if you have code requiring `json/pure` and some other code requiring `json`, you end up with both loaded. If the `json` and `json_pure` versions match, it's not too bad, but if they don't match, you might run into issues with private APIs no longer matching. --- Rakefile | 2 ++ json.gemspec | 6 +++++- json_pure.gemspec | 27 ++++----------------------- lib/json/common.rb | 1 + lib/json/ext.rb | 38 ++++++++++++++++++++------------------ lib/json/ext/.keep | 0 lib/json/generic_object.rb | 1 + lib/json/pure.rb | 23 ++++++++++++----------- lib/json_pure.rb | 3 +++ test/json/test_helper.rb | 30 +++++++++++++++++++++++------- 10 files changed, 71 insertions(+), 60 deletions(-) delete mode 100644 lib/json/ext/.keep create mode 100644 lib/json_pure.rb diff --git a/Rakefile b/Rakefile index 7a013eb0d..9dcdec735 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require 'rubygems/package_task' rescue LoadError diff --git a/json.gemspec b/json.gemspec index c6aa82d36..ff82ec864 100644 --- a/json.gemspec +++ b/json.gemspec @@ -45,7 +45,11 @@ spec = Gem::Specification.new do |s| "LEGAL", "README.md", "json.gemspec", - *Dir["lib/**/*.rb"], + *( + Dir["lib/**/*.rb"] - + # We keep lib/json/pure/*.rb on purpose for TruffleRuby, but not the entry points. + Dir["lib/json/pure.rb"] - Dir["lib/json_pure.rb"] + ), ] if java_ext diff --git a/json_pure.gemspec b/json_pure.gemspec index 37b437c4a..3abf9adaa 100644 --- a/json_pure.gemspec +++ b/json_pure.gemspec @@ -16,36 +16,17 @@ Gem::Specification.new do |s| s.extra_rdoc_files = ["README.md"] s.rdoc_options = ["--title", "JSON implementation for ruby", "--main", "README.md"] + s.files = [ "CHANGES.md", "COPYING", "BSDL", "LEGAL", "README.md", - "json_pure.gemspec", - "lib/json.rb", - "lib/json/add/bigdecimal.rb", - "lib/json/add/complex.rb", - "lib/json/add/core.rb", - "lib/json/add/date.rb", - "lib/json/add/date_time.rb", - "lib/json/add/exception.rb", - "lib/json/add/ostruct.rb", - "lib/json/add/range.rb", - "lib/json/add/rational.rb", - "lib/json/add/regexp.rb", - "lib/json/add/set.rb", - "lib/json/add/struct.rb", - "lib/json/add/symbol.rb", - "lib/json/add/time.rb", - "lib/json/common.rb", - "lib/json/ext.rb", - "lib/json/generic_object.rb", - "lib/json/pure.rb", - "lib/json/pure/generator.rb", - "lib/json/pure/parser.rb", - "lib/json/version.rb", + "json.gemspec", + *(Dir["lib/**/*.rb"] - Dir["lib/json/ext/**/*.rb"]), ] + s.homepage = "https://ruby.github.io/json" s.metadata = { 'bug_tracker_uri' => 'https://github.com/ruby/json/issues', diff --git a/lib/json/common.rb b/lib/json/common.rb index 84f2a57c8..bc18bcb05 100644 --- a/lib/json/common.rb +++ b/lib/json/common.rb @@ -1,4 +1,5 @@ #frozen_string_literal: true + require 'json/version' module JSON diff --git a/lib/json/ext.rb b/lib/json/ext.rb index 92ef61eae..0640b0fba 100644 --- a/lib/json/ext.rb +++ b/lib/json/ext.rb @@ -2,24 +2,26 @@ require 'json/common' -module JSON - # This module holds all the modules/classes that implement JSON's - # functionality as C extensions. - module Ext - if RUBY_ENGINE == 'truffleruby' - require 'json/ext/parser' - require 'json/pure' - $DEBUG and warn "Using Ext extension for JSON parser and Pure library for JSON generator." - JSON.parser = Parser - JSON.generator = JSON::Pure::Generator - else - require 'json/ext/parser' - require 'json/ext/generator' - $DEBUG and warn "Using Ext extension for JSON." - JSON.parser = Parser - JSON.generator = Generator - end +unless defined?(::JSON::JSON_LOADED) + # Prevent json/pure from being loaded at the same time + $LOADED_FEATURES << File.expand_path("../pure.rb", __FILE__) + + if RUBY_ENGINE == 'truffleruby' + require 'json/ext/parser' + # We use require_relative to make sure we're loading the same version. + # Otherwise if the Gemfile include conflicting versions of `json` and `json_pure` + # it may break. + require_relative 'pure/generator' + $DEBUG and warn "Using Ext extension for JSON parser and Pure library for JSON generator." + JSON.parser = JSON::Ext::Parser + JSON.generator = JSON::Pure::Generator + else + require 'json/ext/parser' + require 'json/ext/generator' + $DEBUG and warn "Using Ext extension for JSON." + JSON.parser = JSON::Ext::Parser + JSON.generator = JSON::Ext::Generator end - JSON_LOADED = true unless defined?(::JSON::JSON_LOADED) + ::JSON::JSON_LOADED = true end diff --git a/lib/json/ext/.keep b/lib/json/ext/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/json/generic_object.rb b/lib/json/generic_object.rb index ec5aa9dcb..c53abd7af 100644 --- a/lib/json/generic_object.rb +++ b/lib/json/generic_object.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + begin require 'ostruct' rescue LoadError diff --git a/lib/json/pure.rb b/lib/json/pure.rb index 69d2256d1..db51bb018 100644 --- a/lib/json/pure.rb +++ b/lib/json/pure.rb @@ -1,16 +1,17 @@ # frozen_string_literal: true + require 'json/common' -module JSON - # This module holds all the modules/classes that implement JSON's - # functionality in pure ruby. - module Pure - require 'json/pure/parser' - require 'json/pure/generator' - $DEBUG and warn "Using Pure library for JSON." - JSON.parser = Parser - JSON.generator = Generator - end +unless defined?(::JSON::JSON_LOADED) + require 'json/pure/parser' + require 'json/pure/generator' + + # Prevent json/ext from being loaded at the same time + $LOADED_FEATURES << File.expand_path("../ext.rb", __FILE__) + + $DEBUG and warn "Using Pure library for JSON." + JSON.parser = JSON::Pure::Parser + JSON.generator = JSON::Pure::Generator - JSON_LOADED = true unless defined?(::JSON::JSON_LOADED) + JSON::JSON_LOADED = true end diff --git a/lib/json_pure.rb b/lib/json_pure.rb new file mode 100644 index 000000000..b57d2a523 --- /dev/null +++ b/lib/json_pure.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'json/pure' diff --git a/test/json/test_helper.rb b/test/json/test_helper.rb index f81eeec10..9135cfa32 100644 --- a/test/json/test_helper.rb +++ b/test/json/test_helper.rb @@ -1,23 +1,39 @@ +# frozen_string_literal: true + case ENV['JSON'] when 'pure' $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__)) - $stderr.puts("Testing JSON::Pure") require 'json/pure' + begin + require 'json/ext' + rescue LoadError + # Ensure `json/ext` can't be loaded after `json/pure` + end + if [JSON.generator, JSON.parser].map(&:name) != ["JSON::Pure::Generator", "JSON::Pure::Parser"] + abort "Expected JSON::Pure to be loaded, got: #{[JSON.generator, JSON.parser]}" + end when 'ext' - $stderr.puts("Testing JSON::Ext") $LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__)) require 'json/ext' + require 'json/pure' + + expected = if RUBY_ENGINE == 'truffleruby' + ["JSON::Pure::Generator", "JSON::Ext::Parser"] + else + ["JSON::Ext::Generator", "JSON::Ext::Parser"] + end + + if [JSON.generator, JSON.parser].map(&:name) != expected + abort "Expected JSON::Ext to be loaded, got: #{[JSON.generator, JSON.parser]}" + end else $LOAD_PATH.unshift(File.expand_path('../../../ext', __FILE__), File.expand_path('../../../lib', __FILE__)) - $stderr.puts("Testing JSON") require 'json' end +$stderr.puts("Testing #{JSON.generator} and #{JSON.parser}") + require 'test/unit' -begin - require 'byebug' -rescue LoadError -end if GC.respond_to?(:verify_compaction_references) # This method was added in Ruby 3.0.0. Calling it this way asks the GC to