[ruby/json] Reject incompatible RFC 8785 generation options

https://github.com/ruby/json/commit/49e8df0e16
This commit is contained in:
ydah authored and git committed 2026-10-08 09:57:55 +00:00
1 parent 523192880c
commit 95db6abe45
4 files changed
+80 -1

No files matched your search

+19
View File
@@ -1307,10 +1307,28 @@ static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data
generate_json_general(buffer, data, obj, false);
}
static void validate_rfc8785(JSON_Generator_State *state)
{
if (!state->rfc8785) return;
const char *option = state->indent ? "indent" :
state->space ? "space" :
state->space_before ? "space_before" :
state->object_nl ? "object_nl" :
state->array_nl ? "array_nl" :
state->ascii_only ? "ascii_only" :
state->script_safe ? "script_safe" :
state->allow_nan ? "allow_nan" : NULL;
if (option) {
rb_raise(rb_eArgError, "%s cannot be used with rfc8785", option);
}
}
static VALUE generate_json_try(VALUE d)
{
struct generate_json_data *data = (struct generate_json_data *)d;
validate_rfc8785(data->state);
data->func(data->buffer, data, data->obj);
return fbuffer_finalize(data->buffer);
@@ -1990,6 +2008,7 @@ static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE con
rb_hash_foreach(config, configure_state_i, (VALUE)&data);
raise_argument_error_on_unknown_keywords(data.unknown_keywords);
validate_rfc8785(state);
}
static VALUE cState_configure(VALUE self, VALUE opts)
+4
View File
@@ -418,6 +418,10 @@ require 'json/common'
# as defined in RFC8785.
# Integers are converted to Float before formatting and may be rounded.
# Integers outside the finite Float range raise JSON::GeneratorError.
# Nonempty +indent+, +space+, +space_before+, +object_nl+, or +array_nl+,
# and enabled +ascii_only+, +script_safe+, or +allow_nan+ raise ArgumentError.
# JSON.dump defaults +allow_nan+ to +false+ when +rfc8785+ is enabled.
# JSON::Fragment contents are inserted as is; the caller must ensure they are canonical.
#
# In this example, +obj+ is used first to generate the shortest
# \JSON data (no whitespace), then again with all formatting options
+1 -1
View File
@@ -763,7 +763,7 @@ module JSON
end
opts = {
allow_nan: true,
allow_nan: !(kwargs && kwargs[:rfc8785]),
}
opts[:max_nesting] = _deprecated_limit if _deprecated_limit
opts.merge!(kwargs) if kwargs
+56
View File
@@ -1325,6 +1325,62 @@ class JSONGeneratorTest < Test::Unit::TestCase
end
end
{
indent: ' ', space: ' ', space_before: ' ', object_nl: "\n", array_nl: "\n",
ascii_only: true, script_safe: true, allow_nan: true,
}.each do |option, value|
define_method("test_rfc8785_rejects_#{option}") do
options = { rfc8785: true, option => value }
error = assert_raise(ArgumentError) { JSON.generate({ 'a' => [1] }, options) }
assert_equal "#{option} cannot be used with rfc8785", error.message
assert_raise(ArgumentError) { JSON.generate(nil, options.to_a.reverse.to_h) }
assert_raise(ArgumentError) { [1].to_json(options) }
assert_raise(ArgumentError) { { 'a' => 1 }.to_json(options) }
assert_raise(ArgumentError) { JSON::Coder.new(**options).dump([1]) }
state = JSON::State.new(option => value)
state.rfc8785 = true
io = StringIO.new
assert_raise(ArgumentError) { state.generate([1], io) }
assert_equal '', io.string
assert_raise(ArgumentError) { [1].to_json(state) }
assert_equal 0, state.depth
assert_raise(ArgumentError) { state.freeze.generate([1]) }
end
end
def test_rfc8785_accepts_disabled_options
options = { rfc8785: true, indent: '', space: '', space_before: '',
object_nl: '', array_nl: '', ascii_only: false,
script_safe: false, allow_nan: false }
assert_equal '["é/",1]', JSON.generate(['é/', 1], options)
end
def test_rfc8785_rejects_pretty_generation
assert_raise(ArgumentError) { JSON.pretty_generate({ 'a' => 1 }, rfc8785: true) }
end
def test_rfc8785_dump_disables_allow_nan_by_default
assert_equal '[1]', JSON.dump([1], rfc8785: true)
[Float::NAN, Float::INFINITY, -Float::INFINITY].each do |number|
assert_raise(JSON::GeneratorError) { JSON.dump([number], rfc8785: true) }
end
assert_raise(ArgumentError) { JSON.dump([1], rfc8785: true, allow_nan: true) }
end
def test_rfc8785_fragments
fragment = JSON::Fragment.new('[1,2]')
assert_rfc8785 '[1,2]', fragment
assert_rfc8785 '[[1,2]]', [fragment]
assert_rfc8785 '{"a":[1,2]}', { 'a' => fragment }
assert_equal '[[1,2]]', JSON.generate([fragment], rfc8785: true, max_nesting: false)
assert_equal '[1,2]', JSON::Coder.new(rfc8785: true).dump(fragment)
assert_equal '[1,2]', fragment.to_json(rfc8785: true)
# Fragment contents are the caller's responsibility and are inserted as is.
assert_rfc8785 '[1, 2]', JSON::Fragment.new('[1, 2]')
end
def test_rfc8785_numbers
assert_rfc8785 '-9007199254740992', -9007199254740992
assert_rfc8785 '0', 0