json 2.12.0 → 2.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,188 @@
1
+ typedef enum {
2
+ SIMD_NONE,
3
+ SIMD_NEON,
4
+ SIMD_SSE2
5
+ } SIMD_Implementation;
6
+
7
+ #ifdef JSON_ENABLE_SIMD
8
+
9
+ #ifdef __clang__
10
+ # if __has_builtin(__builtin_ctzll)
11
+ # define HAVE_BUILTIN_CTZLL 1
12
+ # else
13
+ # define HAVE_BUILTIN_CTZLL 0
14
+ # endif
15
+ #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
16
+ # define HAVE_BUILTIN_CTZLL 1
17
+ #else
18
+ # define HAVE_BUILTIN_CTZLL 0
19
+ #endif
20
+
21
+ static inline uint32_t trailing_zeros64(uint64_t input)
22
+ {
23
+ #if HAVE_BUILTIN_CTZLL
24
+ return __builtin_ctzll(input);
25
+ #else
26
+ uint32_t trailing_zeros = 0;
27
+ uint64_t temp = input;
28
+ while ((temp & 1) == 0 && temp > 0) {
29
+ trailing_zeros++;
30
+ temp >>= 1;
31
+ }
32
+ return trailing_zeros;
33
+ #endif
34
+ }
35
+
36
+ static inline int trailing_zeros(int input)
37
+ {
38
+ #if HAVE_BUILTIN_CTZLL
39
+ return __builtin_ctz(input);
40
+ #else
41
+ int trailing_zeros = 0;
42
+ int temp = input;
43
+ while ((temp & 1) == 0 && temp > 0) {
44
+ trailing_zeros++;
45
+ temp >>= 1;
46
+ }
47
+ return trailing_zeros;
48
+ #endif
49
+ }
50
+
51
+ #if (defined(__GNUC__ ) || defined(__clang__))
52
+ #define FORCE_INLINE __attribute__((always_inline))
53
+ #else
54
+ #define FORCE_INLINE
55
+ #endif
56
+
57
+
58
+ #define SIMD_MINIMUM_THRESHOLD 6
59
+
60
+ #if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64)
61
+ #include <arm_neon.h>
62
+
63
+ #define FIND_SIMD_IMPLEMENTATION_DEFINED 1
64
+ static inline SIMD_Implementation find_simd_implementation(void)
65
+ {
66
+ return SIMD_NEON;
67
+ }
68
+
69
+ #define HAVE_SIMD 1
70
+ #define HAVE_SIMD_NEON 1
71
+
72
+ // See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
73
+ static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches)
74
+ {
75
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4);
76
+ const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
77
+ return mask & 0x8888888888888888ull;
78
+ }
79
+
80
+ static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr)
81
+ {
82
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
83
+
84
+ // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
85
+ // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
86
+ const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
87
+
88
+ uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
89
+ uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
90
+ return neon_match_mask(needs_escape);
91
+ }
92
+
93
+ static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask)
94
+ {
95
+ while (*ptr + sizeof(uint8x16_t) <= end) {
96
+ uint64_t chunk_mask = compute_chunk_mask_neon(*ptr);
97
+ if (chunk_mask) {
98
+ *mask = chunk_mask;
99
+ return 1;
100
+ }
101
+ *ptr += sizeof(uint8x16_t);
102
+ }
103
+ return 0;
104
+ }
105
+
106
+ static inline uint8x16x4_t load_uint8x16_4(const unsigned char *table)
107
+ {
108
+ uint8x16x4_t tab;
109
+ tab.val[0] = vld1q_u8(table);
110
+ tab.val[1] = vld1q_u8(table+16);
111
+ tab.val[2] = vld1q_u8(table+32);
112
+ tab.val[3] = vld1q_u8(table+48);
113
+ return tab;
114
+ }
115
+
116
+ #endif /* ARM Neon Support.*/
117
+
118
+ #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)
119
+
120
+ #ifdef HAVE_X86INTRIN_H
121
+ #include <x86intrin.h>
122
+
123
+ #define HAVE_SIMD 1
124
+ #define HAVE_SIMD_SSE2 1
125
+
126
+ #ifdef HAVE_CPUID_H
127
+ #define FIND_SIMD_IMPLEMENTATION_DEFINED 1
128
+
129
+ #if defined(__clang__) || defined(__GNUC__)
130
+ #define TARGET_SSE2 __attribute__((target("sse2")))
131
+ #else
132
+ #define TARGET_SSE2
133
+ #endif
134
+
135
+ #define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
136
+ #define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
137
+ #define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
138
+ #define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
139
+
140
+ static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *ptr)
141
+ {
142
+ __m128i chunk = _mm_loadu_si128((__m128i const*)ptr);
143
+ // Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
144
+ // https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
145
+ __m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33));
146
+ __m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
147
+ __m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash);
148
+ return _mm_movemask_epi8(needs_escape);
149
+ }
150
+
151
+ static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask)
152
+ {
153
+ while (*ptr + sizeof(__m128i) <= end) {
154
+ int chunk_mask = compute_chunk_mask_sse2(*ptr);
155
+ if (chunk_mask) {
156
+ *mask = chunk_mask;
157
+ return 1;
158
+ }
159
+ *ptr += sizeof(__m128i);
160
+ }
161
+
162
+ return 0;
163
+ }
164
+
165
+ #include <cpuid.h>
166
+ #endif /* HAVE_CPUID_H */
167
+
168
+ static inline SIMD_Implementation find_simd_implementation(void)
169
+ {
170
+ // TODO Revisit. I think the SSE version now only uses SSE2 instructions.
171
+ if (__builtin_cpu_supports("sse2")) {
172
+ return SIMD_SSE2;
173
+ }
174
+
175
+ return SIMD_NONE;
176
+ }
177
+
178
+ #endif /* HAVE_X86INTRIN_H */
179
+ #endif /* X86_64 Support */
180
+
181
+ #endif /* JSON_ENABLE_SIMD */
182
+
183
+ #ifndef FIND_SIMD_IMPLEMENTATION_DEFINED
184
+ static inline SIMD_Implementation find_simd_implementation(void)
185
+ {
186
+ return SIMD_NONE;
187
+ }
188
+ #endif
@@ -92,7 +92,7 @@ static Fp find_cachedpow10(int exp, int* k)
92
92
  {
93
93
  const double one_log_ten = 0.30102999566398114;
94
94
 
95
- int approx = -(exp + npowers) * one_log_ten;
95
+ int approx = (int)(-(exp + npowers) * one_log_ten);
96
96
  int idx = (approx - firstpower) / steppowers;
97
97
 
98
98
  while(1) {
@@ -432,8 +432,8 @@ static int filter_special(double fp, char* dest)
432
432
  *
433
433
  * Input:
434
434
  * fp -> the double to convert, dest -> destination buffer.
435
- * The generated string will never be longer than 24 characters.
436
- * Make sure to pass a pointer to at least 24 bytes of memory.
435
+ * The generated string will never be longer than 28 characters.
436
+ * Make sure to pass a pointer to at least 28 bytes of memory.
437
437
  * The emitted string will not be null terminated.
438
438
  *
439
439
  * Output:
@@ -443,7 +443,7 @@ static int filter_special(double fp, char* dest)
443
443
  *
444
444
  * void print(double d)
445
445
  * {
446
- * char buf[24 + 1] // plus null terminator
446
+ * char buf[28 + 1] // plus null terminator
447
447
  * int str_len = fpconv_dtoa(d, buf);
448
448
  *
449
449
  * buf[str_len] = '\0';
@@ -451,7 +451,7 @@ static int filter_special(double fp, char* dest)
451
451
  * }
452
452
  *
453
453
  */
454
- static int fpconv_dtoa(double d, char dest[24])
454
+ static int fpconv_dtoa(double d, char dest[28])
455
455
  {
456
456
  char digits[18];
457
457
 
data/json.gemspec CHANGED
@@ -44,15 +44,14 @@ spec = Gem::Specification.new do |s|
44
44
  "LEGAL",
45
45
  "README.md",
46
46
  "json.gemspec",
47
- *Dir["lib/**/*.rb"],
48
- ]
47
+ ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__))
49
48
 
50
49
  if java_ext
51
50
  s.platform = 'java'
52
51
  s.files += Dir["lib/json/ext/**/*.jar"]
53
52
  else
54
53
  s.extensions = Dir["ext/json/**/extconf.rb"]
55
- s.files += Dir["ext/json/**/*.{c,h}"]
54
+ s.files += Dir["ext/json/**/*.{c,h,rb}"]
56
55
  end
57
56
  end
58
57
 
data/lib/json/common.rb CHANGED
@@ -48,7 +48,7 @@ module JSON
48
48
  end
49
49
  end
50
50
 
51
- # TODO: exctract :create_additions support to another gem for version 3.0
51
+ # TODO: extract :create_additions support to another gem for version 3.0
52
52
  def create_additions_proc(opts)
53
53
  if opts[:symbolize_names]
54
54
  raise ArgumentError, "options :symbolize_names and :create_additions cannot be used in conjunction"
@@ -87,31 +87,32 @@ module JSON
87
87
  opts
88
88
  end
89
89
 
90
- GEM_ROOT = File.expand_path("../../../", __FILE__) + "/"
91
90
  def create_additions_warning
92
- message = "JSON.load implicit support for `create_additions: true` is deprecated " \
91
+ JSON.deprecation_warning "JSON.load implicit support for `create_additions: true` is deprecated " \
93
92
  "and will be removed in 3.0, use JSON.unsafe_load or explicitly " \
94
93
  "pass `create_additions: true`"
94
+ end
95
+ end
96
+ end
95
97
 
96
- uplevel = 4
97
- caller_locations(uplevel, 10).each do |frame|
98
- if frame.path.nil? || frame.path.start_with?(GEM_ROOT) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
99
- uplevel += 1
100
- else
101
- break
102
- end
103
- end
104
-
105
- if RUBY_VERSION >= "3.0"
106
- warn(message, uplevel: uplevel - 1, category: :deprecated)
98
+ class << self
99
+ def deprecation_warning(message, uplevel = 3) # :nodoc:
100
+ gem_root = File.expand_path("../../../", __FILE__) + "/"
101
+ caller_locations(uplevel, 10).each do |frame|
102
+ if frame.path.nil? || frame.path.start_with?(gem_root) || frame.path.end_with?("/truffle/cext_ruby.rb", ".c")
103
+ uplevel += 1
107
104
  else
108
- warn(message, uplevel: uplevel - 1)
105
+ break
109
106
  end
110
107
  end
108
+
109
+ if RUBY_VERSION >= "3.0"
110
+ warn(message, uplevel: uplevel, category: :deprecated)
111
+ else
112
+ warn(message, uplevel: uplevel)
113
+ end
111
114
  end
112
- end
113
115
 
114
- class << self
115
116
  # :call-seq:
116
117
  # JSON[object] -> new_array or new_string
117
118
  #
@@ -172,7 +173,7 @@ module JSON
172
173
  end
173
174
  end
174
175
  self.state = generator::State
175
- const_set :State, self.state
176
+ const_set :State, state
176
177
  ensure
177
178
  $VERBOSE = old
178
179
  end
@@ -268,7 +269,7 @@ module JSON
268
269
  # to string interpolation.
269
270
  #
270
271
  # Note: no validation is performed on the provided string. It is the
271
- # responsability of the caller to ensure the string contains valid JSON.
272
+ # responsibility of the caller to ensure the string contains valid JSON.
272
273
  Fragment = Struct.new(:json) do
273
274
  def initialize(json)
274
275
  unless string = String.try_convert(json)
@@ -490,7 +491,7 @@ module JSON
490
491
  # }
491
492
  #
492
493
  def pretty_generate(obj, opts = nil)
493
- return state.generate(obj) if State === opts
494
+ return opts.generate(obj) if State === opts
494
495
 
495
496
  options = PRETTY_GENERATE_OPTIONS
496
497
 
@@ -1072,7 +1073,7 @@ module ::Kernel
1072
1073
  end
1073
1074
 
1074
1075
  objs.each do |obj|
1075
- puts JSON::generate(obj, :allow_nan => true, :max_nesting => false)
1076
+ puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
1076
1077
  end
1077
1078
  nil
1078
1079
  end
@@ -1087,7 +1088,7 @@ module ::Kernel
1087
1088
  end
1088
1089
 
1089
1090
  objs.each do |obj|
1090
- puts JSON::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1091
+ puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
1091
1092
  end
1092
1093
  nil
1093
1094
  end
data/lib/json/ext.rb CHANGED
@@ -34,12 +34,12 @@ module JSON
34
34
 
35
35
  if RUBY_ENGINE == 'truffleruby'
36
36
  require 'json/truffle_ruby/generator'
37
- JSON.generator = ::JSON::TruffleRuby::Generator
37
+ JSON.generator = JSON::TruffleRuby::Generator
38
38
  else
39
39
  require 'json/ext/generator'
40
40
  JSON.generator = Generator
41
41
  end
42
42
  end
43
43
 
44
- JSON_LOADED = true unless defined?(::JSON::JSON_LOADED)
44
+ JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
45
45
  end
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.12.0'
4
+ VERSION = '2.13.2'
5
5
  end
data/lib/json.rb CHANGED
@@ -127,6 +127,24 @@ require 'json/common'
127
127
  #
128
128
  # ---
129
129
  #
130
+ # Option +allow_duplicate_key+ specifies whether duplicate keys in objects
131
+ # should be ignored or cause an error to be raised:
132
+ #
133
+ # When not specified:
134
+ # # The last value is used and a deprecation warning emitted.
135
+ # JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
136
+ # # waring: detected duplicate keys in JSON object.
137
+ # # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
138
+ #
139
+ # When set to `+true+`
140
+ # # The last value is used.
141
+ # JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
142
+ #
143
+ # When set to `+false+`, the future default:
144
+ # JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError)
145
+ #
146
+ # ---
147
+ #
130
148
  # Option +allow_nan+ (boolean) specifies whether to allow
131
149
  # NaN, Infinity, and MinusInfinity in +source+;
132
150
  # defaults to +false+.
@@ -143,8 +161,23 @@ require 'json/common'
143
161
  # ruby = JSON.parse(source, {allow_nan: true})
144
162
  # ruby # => [NaN, Infinity, -Infinity]
145
163
  #
164
+ # ---
165
+ #
166
+ # Option +allow_trailing_comma+ (boolean) specifies whether to allow
167
+ # trailing commas in objects and arrays;
168
+ # defaults to +false+.
169
+ #
170
+ # With the default, +false+:
171
+ # JSON.parse('[1,]') # unexpected character: ']' at line 1 column 4 (JSON::ParserError)
172
+ #
173
+ # When enabled:
174
+ # JSON.parse('[1,]', allow_trailing_comma: true) # => [1]
175
+ #
146
176
  # ====== Output Options
147
177
  #
178
+ # Option +freeze+ (boolean) specifies whether the returned objects will be frozen;
179
+ # defaults to +false+.
180
+ #
148
181
  # Option +symbolize_names+ (boolean) specifies whether returned \Hash keys
149
182
  # should be Symbols;
150
183
  # defaults to +false+ (use Strings).
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.12.0
4
+ version: 2.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Frank
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-12 00:00:00.000000000 Z
10
+ date: 2025-07-28 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: This is a JSON implementation as a Ruby extension in C.
13
13
  email: flori@ping.de
@@ -26,9 +26,10 @@ files:
26
26
  - ext/json/ext/fbuffer/fbuffer.h
27
27
  - ext/json/ext/generator/extconf.rb
28
28
  - ext/json/ext/generator/generator.c
29
- - ext/json/ext/generator/simd.h
30
29
  - ext/json/ext/parser/extconf.rb
31
30
  - ext/json/ext/parser/parser.c
31
+ - ext/json/ext/simd/conf.rb
32
+ - ext/json/ext/simd/simd.h
32
33
  - ext/json/ext/vendor/fpconv.c
33
34
  - ext/json/ext/vendor/jeaiii-ltoa.h
34
35
  - json.gemspec
@@ -1,112 +0,0 @@
1
- typedef enum {
2
- SIMD_NONE,
3
- SIMD_NEON,
4
- SIMD_SSE2
5
- } SIMD_Implementation;
6
-
7
- #ifdef JSON_ENABLE_SIMD
8
-
9
- #ifdef __clang__
10
- #if __has_builtin(__builtin_ctzll)
11
- #define HAVE_BUILTIN_CTZLL 1
12
- #else
13
- #define HAVE_BUILTIN_CTZLL 0
14
- #endif
15
- #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
16
- #define HAVE_BUILTIN_CTZLL 1
17
- #else
18
- #define HAVE_BUILTIN_CTZLL 0
19
- #endif
20
-
21
- static inline uint32_t trailing_zeros64(uint64_t input) {
22
- #if HAVE_BUILTIN_CTZLL
23
- return __builtin_ctzll(input);
24
- #else
25
- uint32_t trailing_zeros = 0;
26
- uint64_t temp = input;
27
- while ((temp & 1) == 0 && temp > 0) {
28
- trailing_zeros++;
29
- temp >>= 1;
30
- }
31
- return trailing_zeros;
32
- #endif
33
- }
34
-
35
- static inline int trailing_zeros(int input) {
36
- #if HAVE_BUILTIN_CTZLL
37
- return __builtin_ctz(input);
38
- #else
39
- int trailing_zeros = 0;
40
- int temp = input;
41
- while ((temp & 1) == 0 && temp > 0) {
42
- trailing_zeros++;
43
- temp >>= 1;
44
- }
45
- return trailing_zeros;
46
- #endif
47
- }
48
-
49
- #define SIMD_MINIMUM_THRESHOLD 6
50
-
51
- #if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64)
52
- #include <arm_neon.h>
53
-
54
- #define FIND_SIMD_IMPLEMENTATION_DEFINED 1
55
- static SIMD_Implementation find_simd_implementation(void) {
56
- return SIMD_NEON;
57
- }
58
-
59
- #define HAVE_SIMD 1
60
- #define HAVE_SIMD_NEON 1
61
-
62
- uint8x16x4_t load_uint8x16_4(const unsigned char *table) {
63
- uint8x16x4_t tab;
64
- tab.val[0] = vld1q_u8(table);
65
- tab.val[1] = vld1q_u8(table+16);
66
- tab.val[2] = vld1q_u8(table+32);
67
- tab.val[3] = vld1q_u8(table+48);
68
- return tab;
69
- }
70
-
71
- #endif /* ARM Neon Support.*/
72
-
73
- #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)
74
-
75
- #ifdef HAVE_X86INTRIN_H
76
- #include <x86intrin.h>
77
-
78
- #define HAVE_SIMD 1
79
- #define HAVE_SIMD_SSE2 1
80
-
81
- #ifdef HAVE_CPUID_H
82
- #define FIND_SIMD_IMPLEMENTATION_DEFINED 1
83
-
84
- #include <cpuid.h>
85
- #endif /* HAVE_CPUID_H */
86
-
87
- static SIMD_Implementation find_simd_implementation(void) {
88
-
89
- #if defined(__GNUC__ ) || defined(__clang__)
90
- #ifdef __GNUC__
91
- __builtin_cpu_init();
92
- #endif /* __GNUC__ */
93
-
94
- // TODO Revisit. I think the SSE version now only uses SSE2 instructions.
95
- if (__builtin_cpu_supports("sse2")) {
96
- return SIMD_SSE2;
97
- }
98
- #endif /* __GNUC__ || __clang__*/
99
-
100
- return SIMD_NONE;
101
- }
102
-
103
- #endif /* HAVE_X86INTRIN_H */
104
- #endif /* X86_64 Support */
105
-
106
- #endif /* JSON_ENABLE_SIMD */
107
-
108
- #ifndef FIND_SIMD_IMPLEMENTATION_DEFINED
109
- static SIMD_Implementation find_simd_implementation(void) {
110
- return SIMD_NONE;
111
- }
112
- #endif