diff --git a/.gitignore b/.gitignore index 3e8d3310f555b4..ddf8e9a99adae3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ *.pch *.pdb *.rbinc +*.rbbin *.rej *.s *.sav diff --git a/ext/json/lib/json/common.rb b/ext/json/lib/json/common.rb index 486ec62a582450..9a878cead9f6a3 100644 --- a/ext/json/lib/json/common.rb +++ b/ext/json/lib/json/common.rb @@ -48,7 +48,7 @@ def array_class_proc(array_class, on_load) end end - # TODO: exctract :create_additions support to another gem for version 3.0 + # TODO: extract :create_additions support to another gem for version 3.0 def create_additions_proc(opts) if opts[:symbolize_names] raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction" @@ -87,31 +87,32 @@ def create_additions_proc(opts) opts end - GEM_ROOT = File.expand_path("../../../", __FILE__) + "/" def create_additions_warning - message = "JSON.load implicit support for `create_additions: true` is deprecated " \ + JSON.deprecation_warning "JSON.load implicit support for `create_additions: true` is deprecated " \ "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \ "pass `create_additions: true`" + end + end + end - uplevel = 4 - caller_locations(uplevel, 10).each do |frame| - if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c") - uplevel += 1 - else - break - end - end - - if RUBY_VERSION >= "3.0" - warn(message, uplevel: uplevel - 1, category: :deprecated) + class << self + def deprecation_warning(message, uplevel = 3) # :nodoc: + gem_root = File.expand_path("../../../", __FILE__) + "/" + caller_locations(uplevel, 10).each do |frame| + if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c") + uplevel += 1 else - warn(message, uplevel: uplevel - 1) + break end end + + if RUBY_VERSION >= "3.0" + warn(message, uplevel: uplevel, category: :deprecated) + else + warn(message, uplevel: uplevel) + end end - end - class << self # :call-seq: # JSON[object] -> new_array or new_string # diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 01b6e6293b7aaf..ab9d6c205e69b2 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -422,10 +422,12 @@ static void emit_parse_warning(const char *message, JSON_ParserState *state) long line, column; cursor_position(state, &line, &column); - rb_warn("%s at line %ld column %ld", message, line, column); + VALUE warning = rb_sprintf("%s at line %ld column %ld", message, line, column); + rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning); } #define PARSE_ERROR_FRAGMENT_LEN 32 + #ifdef RBIMPL_ATTR_NORETURN RBIMPL_ATTR_NORETURN() #endif @@ -830,21 +832,64 @@ static inline VALUE json_decode_array(JSON_ParserState *state, JSON_ParserConfig return array; } +static VALUE json_find_duplicated_key(size_t count, const VALUE *pairs) +{ + VALUE set = rb_hash_new_capa(count / 2); + for (size_t index = 0; index < count; index += 2) { + size_t before = RHASH_SIZE(set); + VALUE key = pairs[index]; + rb_hash_aset(set, key, Qtrue); + if (RHASH_SIZE(set) == before) { + if (RB_SYMBOL_P(key)) { + return rb_sym2str(key); + } + return key; + } + } + return Qfalse; +} + +static void emit_duplicate_key_warning(JSON_ParserState *state, VALUE duplicate_key) +{ + VALUE message = rb_sprintf( + "detected duplicate key %"PRIsVALUE" in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", + rb_inspect(duplicate_key) + ); + + emit_parse_warning(RSTRING_PTR(message), state); + RB_GC_GUARD(message); +} + +#ifdef RBIMPL_ATTR_NORETURN +RBIMPL_ATTR_NORETURN() +#endif +static void raise_duplicate_key_error(JSON_ParserState *state, VALUE duplicate_key) +{ + VALUE message = rb_sprintf( + "duplicate key %"PRIsVALUE, + rb_inspect(duplicate_key) + ); + + raise_parse_error(RSTRING_PTR(message), state); + RB_GC_GUARD(message); +} + static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfig *config, size_t count) { size_t entries_count = count / 2; VALUE object = rb_hash_new_capa(entries_count); - rb_hash_bulk_insert(count, rvalue_stack_peek(state->stack, count), object); + const VALUE *pairs = rvalue_stack_peek(state->stack, count); + rb_hash_bulk_insert(count, pairs, object); if (RB_UNLIKELY(RHASH_SIZE(object) < entries_count)) { switch (config->on_duplicate_key) { case JSON_IGNORE: break; case JSON_DEPRECATED: - emit_parse_warning("detected duplicate keys in JSON object. This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`", state); + emit_duplicate_key_warning(state, json_find_duplicated_key(count, pairs)); break; case JSON_RAISE: - raise_parse_error("duplicate key", state); + raise_duplicate_key_error(state, json_find_duplicated_key(count, pairs)); break; } } diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index be21ba8299370f..7608adf2ef9e9c 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -130,7 +130,7 @@ def self.patch_level_options(options) def self.clean_after_install? clean = Bundler.settings[:clean] return clean unless clean.nil? - clean ||= Bundler.feature_flag.bundler_4_mode? && Bundler.settings[:path].nil? + clean ||= Bundler.feature_flag.bundler_5_mode? && Bundler.settings[:path].nil? clean &&= !Bundler.use_system_gems? clean end diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index a8fa2a1bdee04a..2cf58eefd60f27 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -30,7 +30,7 @@ def self.settings_method(name, key, &default) settings_flag(:allow_offline_install) { bundler_4_mode? } settings_flag(:cache_all) { bundler_4_mode? } settings_flag(:forget_cli_options) { bundler_4_mode? } - settings_flag(:global_gem_cache) { bundler_4_mode? } + settings_flag(:global_gem_cache) { bundler_5_mode? } settings_flag(:lockfile_checksums) { bundler_4_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } settings_flag(:update_requires_all_flag) { bundler_5_mode? } diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index d036754a78371a..cc20598fd9c2c2 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -272,7 +272,7 @@ def path def use_system_gems? return true if system_path return false if explicit_path - !Bundler.feature_flag.bundler_4_mode? + !Bundler.feature_flag.bundler_5_mode? end def base_path diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 99a8f9c1416e5f..fd5a0fdbcaa2ff 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -151,7 +151,7 @@ def should_not_have_gems(*gems) bundle :clean digest = Digest(:SHA1).hexdigest(git_path.to_s) - cache_path = Bundler.feature_flag.global_gem_cache? ? home(".bundle/cache/git/foo-1.0-#{digest}") : vendored_gems("cache/bundler/git/foo-1.0-#{digest}") + cache_path = vendored_gems("cache/bundler/git/foo-1.0-#{digest}") expect(cache_path).to exist end @@ -427,7 +427,7 @@ def should_not_have_gems(*gems) should_not_have_gems "foo-1.0" end - it "automatically cleans when path has not been set", bundler: "4" do + it "automatically cleans when path has not been set", bundler: "5" do build_repo2 install_gemfile <<-G diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 22eb64ca81a0e0..e7c1f5e456e2d2 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -30,22 +30,29 @@ end it "does not create ./.bundle by default" do - gemfile <<-G + install_gemfile <<-G source "https://gem.repo1" gem "myrack" G - bundle :install # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end + it "will create a ./.bundle by default", bundler: "5" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + expect(bundled_app(".bundle")).to exist + end + it "does not create ./.bundle by default when installing to system gems" do - gemfile <<-G + install_gemfile <<-G, env: { "BUNDLE_PATH__SYSTEM" => "true" } source "https://gem.repo1" gem "myrack" G - bundle :install, env: { "BUNDLE_PATH__SYSTEM" => "true" } # can't use install_gemfile since it sets retry expect(bundled_app(".bundle")).not_to exist end diff --git a/spec/bundler/install/gems/compact_index_spec.rb b/spec/bundler/install/gems/compact_index_spec.rb index 823b5aab110b03..e5f7380855befd 100644 --- a/spec/bundler/install/gems/compact_index_spec.rb +++ b/spec/bundler/install/gems/compact_index_spec.rb @@ -1010,11 +1010,7 @@ def start gem "myrack" G - gem_path = if Bundler.feature_flag.global_gem_cache? - default_cache_path.dirname.join("cache", "gems", "localgemserver.test.80.dd34752a738ee965a2a4298dc16db6c5", "myrack-1.0.0.gem") - else - default_cache_path.dirname.join("myrack-1.0.0.gem") - end + gem_path = default_cache_path.dirname.join("myrack-1.0.0.gem") expect(exitstatus).to eq(37) expect(err).to eq <<~E.strip diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb index 15b4adf62e387b..6e94b10e32c845 100644 --- a/spec/bundler/support/filters.rb +++ b/spec/bundler/support/filters.rb @@ -32,7 +32,7 @@ def inspect config.filter_run_when_matching :focus unless ENV["CI"] - config.before(:each, bundler: "4") do - bundle "config simulate_version 4" + config.before(:each, :bundler) do |example| + bundle "config simulate_version #{example.metadata[:bundler]}" end end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 7c2d06abaf55b8..cc750f55d81f53 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -136,19 +136,11 @@ def home(*path) end def default_bundle_path(*path) - if Bundler.feature_flag.bundler_4_mode? - local_gem_path(*path) - else - system_gem_path(*path) - end + system_gem_path(*path) end def default_cache_path(*path) - if Bundler.feature_flag.global_gem_cache? - home(".bundle/cache", *path) - else - default_bundle_path("cache/bundler", *path) - end + default_bundle_path("cache/bundler", *path) end def compact_index_cache_path diff --git a/test/json/json_parser_test.rb b/test/json/json_parser_test.rb index 106492e1c41a9c..6455d2971ae3c0 100644 --- a/test/json/json_parser_test.rb +++ b/test/json/json_parser_test.rb @@ -333,11 +333,36 @@ def test_parse_big_integers def test_parse_duplicate_key expected = {"a" => 2} + expected_sym = {a: 2} + assert_equal expected, parse('{"a": 1, "a": 2}', allow_duplicate_key: true) assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false) } - assert_deprecated_warning(/duplicate keys/) do + assert_raise(ParserError) { parse('{"a": 1, "a": 2}', allow_duplicate_key: false, symbolize_names: true) } + + assert_deprecated_warning(/duplicate key "a"/) do assert_equal expected, parse('{"a": 1, "a": 2}') end + assert_deprecated_warning(/duplicate key "a"/) do + assert_equal expected_sym, parse('{"a": 1, "a": 2}', symbolize_names: true) + end + + if RUBY_ENGINE == 'RUBY_ENGINE' + assert_deprecated_warning(/#{File.basename(__FILE__)}\:#{__LINE__ + 1}/) do + assert_equal expected, parse('{"a": 1, "a": 2}') + end + end + + unless RUBY_ENGINE == 'jruby' + assert_raise(ParserError) do + fake_key = Object.new + JSON.load('{"a": 1, "a": 2}', -> (obj) { obj == "a" ? fake_key : obj }, allow_duplicate_key: false) + end + + assert_deprecated_warning(/duplicate key # (obj) { obj == "a" ? fake_key : obj }) + end + end end def test_some_wrong_inputs