From c24342d801f4b5f9ab5ca37bdc2449361d3635b5 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 23 Jul 2025 10:27:08 -0700 Subject: [PATCH 1/3] Fix missing write barrier on Generator State Found by wbcheck WBCHECK ERROR: Missed write barrier detected! Parent object: 0x7b7b8487c450 (wb_protected: true) rb_obj_info_dump: 0x00007b7b8487c450 JSON/Generator/State/JSON::Ext::Generator::State JSON/Generator/State Reference counts - snapshot: 1, writebarrier: 0, current: 6, missed: 5 Missing reference to: 0x7b7b82f35a10 rb_obj_info_dump: 0x00007b7b82f35a10 T_STRING/String len: 1, capa: 15 "1" Missing reference to: 0x7b7b82f35e90 rb_obj_info_dump: 0x00007b7b82f35e90 T_STRING/String len: 1, capa: 15 "2" Missing reference to: 0x7b7b83629e50 rb_obj_info_dump: 0x00007b7b83629e50 T_STRING/String len: 1, capa: 15 "3" Missing reference to: 0x7b7b83b62190 rb_obj_info_dump: 0x00007b7b83b62190 T_STRING/String len: 1, capa: 15 "4" Missing reference to: 0x7b7b83629490 rb_obj_info_dump: 0x00007b7b83629490 T_STRING/String len: 1, capa: 15 "5" --- ext/json/ext/generator/generator.c | 45 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/ext/json/ext/generator/generator.c b/ext/json/ext/generator/generator.c index 08338853..9f56cf7f 100644 --- a/ext/json/ext/generator/generator.c +++ b/ext/json/ext/generator/generator.c @@ -1609,15 +1609,30 @@ static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_l return Qnil; } +struct configure_state_data { + JSON_Generator_State *state; + VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated +}; + +static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value) +{ + if (RTEST(data->vstate)) { + RB_OBJ_WRITE(data->vstate, field, value); + } else { + *field = value; + } +} + static int configure_state_i(VALUE key, VALUE val, VALUE _arg) { - JSON_Generator_State *state = (JSON_Generator_State *)_arg; + struct configure_state_data *data = (struct configure_state_data *)_arg; + JSON_Generator_State *state = data->state; - if (key == sym_indent) { state->indent = string_config(val); } - else if (key == sym_space) { state->space = string_config(val); } - else if (key == sym_space_before) { state->space_before = string_config(val); } - else if (key == sym_object_nl) { state->object_nl = string_config(val); } - else if (key == sym_array_nl) { state->array_nl = string_config(val); } + if (key == sym_indent) { state_write_value(data, &state->indent, string_config(val)); } + else if (key == sym_space) { state_write_value(data, &state->space, string_config(val)); } + else if (key == sym_space_before) { state_write_value(data, &state->space_before, string_config(val)); } + else if (key == sym_object_nl) { state_write_value(data, &state->object_nl, string_config(val)); } + else if (key == sym_array_nl) { state_write_value(data, &state->array_nl, string_config(val)); } else if (key == sym_max_nesting) { state->max_nesting = long_config(val); } else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); } else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); } @@ -1626,11 +1641,14 @@ static int configure_state_i(VALUE key, VALUE val, VALUE _arg) else if (key == sym_script_safe) { state->script_safe = RTEST(val); } else if (key == sym_escape_slash) { state->script_safe = RTEST(val); } else if (key == sym_strict) { state->strict = RTEST(val); } - else if (key == sym_as_json) { state->as_json = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; } + else if (key == sym_as_json) { + VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse; + state_write_value(data, &state->as_json, proc); + } return ST_CONTINUE; } -static void configure_state(JSON_Generator_State *state, VALUE config) +static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE config) { if (!RTEST(config)) return; @@ -1638,15 +1656,20 @@ static void configure_state(JSON_Generator_State *state, VALUE config) if (!RHASH_SIZE(config)) return; + struct configure_state_data data = { + .state = state, + .vstate = vstate + }; + // We assume in most cases few keys are set so it's faster to go over // the provided keys than to check all possible keys. - rb_hash_foreach(config, configure_state_i, (VALUE)state); + rb_hash_foreach(config, configure_state_i, (VALUE)&data); } static VALUE cState_configure(VALUE self, VALUE opts) { GET_STATE(self); - configure_state(state, opts); + configure_state(state, self, opts); return self; } @@ -1654,7 +1677,7 @@ static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io) { JSON_Generator_State state = {0}; state_init(&state); - configure_state(&state, opts); + configure_state(&state, Qfalse, opts); char stack_buffer[FBUFFER_STACK_SIZE]; FBuffer buffer = { From da878435dc44c2c4b5a9890e5557170dc310347d Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 24 Jul 2025 13:24:55 +0200 Subject: [PATCH 2/3] Don't assume `__builtin_cpu_supports` exists Fix: https://github.com/ruby/json/issues/827 On very old compilers it might not exist, at that point might as well skip SIMD entirely. --- CHANGES.md | 2 ++ ext/json/ext/simd/conf.rb | 26 +++++++++++++++----------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 562bdb6f..697de4e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Changes +* Fix support for older compilers without `__builtin_cpu_supports`. + ### Unreleased ### 2025-05-23 (2.13.0) diff --git a/ext/json/ext/simd/conf.rb b/ext/json/ext/simd/conf.rb index 8e7d8ee2..76f774bc 100644 --- a/ext/json/ext/simd/conf.rb +++ b/ext/json/ext/simd/conf.rb @@ -1,20 +1,24 @@ case RbConfig::CONFIG['host_cpu'] when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions - header, type, init = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)' + header, type, init, extra = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)', nil when /^(x86_64|x64)/ - header, type, init = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)' + header, type, init, extra = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)', 'if (__builtin_cpu_supports("sse2")) { printf("OK"); }' end if header - have_header(header) && try_compile(<<~SRC) - #{cpp_include(header)} - int main(int argc, char **argv) { - #{type} test = #{init}; - if (argc > 100000) printf("%p", &test); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") + if have_header(header) && try_compile(<<~SRC, '-Werror=implicit-function-declaration') + #{cpp_include(header)} + int main(int argc, char **argv) { + #{type} test = #{init}; + #{extra} + if (argc > 100000) printf("%p", &test); + return 0; + } + SRC + $defs.push("-DJSON_ENABLE_SIMD") + else + puts "Disable SIMD" + end end have_header('cpuid.h') From cfe9337eda64a1bd7edad53053b0079c33868d9e Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 24 Jul 2025 14:05:58 +0200 Subject: [PATCH 3/3] Release 2.13.1 --- CHANGES.md | 8 +++++--- lib/json/version.rb | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 697de4e7..58e3f60d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,12 @@ # Changes -* Fix support for older compilers without `__builtin_cpu_supports`. - ### Unreleased -### 2025-05-23 (2.13.0) +### 2025-07-24 (2.13.1) + +* Fix support for older compilers without `__builtin_cpu_supports`. + +### 2025-07-17 (2.13.0) * Add new `allow_duplicate_key` parsing options. By default a warning is now emitted when a duplicated key is encountered. In `json 3.0` an error will be raised. diff --git a/lib/json/version.rb b/lib/json/version.rb index 7aeaea43..2aef3d7f 100644 --- a/lib/json/version.rb +++ b/lib/json/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module JSON - VERSION = '2.13.0' + VERSION = '2.13.1' end