oj 3.16.0 → 3.17.3

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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +107 -0
  3. data/README.md +0 -16
  4. data/ext/oj/cache.c +4 -2
  5. data/ext/oj/cache.h +3 -2
  6. data/ext/oj/compat.c +3 -3
  7. data/ext/oj/custom.c +17 -11
  8. data/ext/oj/dump.c +624 -90
  9. data/ext/oj/dump.h +9 -2
  10. data/ext/oj/dump_compat.c +15 -9
  11. data/ext/oj/dump_leaf.c +1 -1
  12. data/ext/oj/dump_object.c +32 -17
  13. data/ext/oj/dump_strict.c +20 -14
  14. data/ext/oj/extconf.rb +11 -9
  15. data/ext/oj/fast.c +50 -32
  16. data/ext/oj/intern.c +24 -7
  17. data/ext/oj/mimic_json.c +17 -11
  18. data/ext/oj/object.c +21 -13
  19. data/ext/oj/oj.c +387 -140
  20. data/ext/oj/oj.h +59 -55
  21. data/ext/oj/parse.c +248 -47
  22. data/ext/oj/parser.c +179 -89
  23. data/ext/oj/parser.h +4 -2
  24. data/ext/oj/rails.c +64 -42
  25. data/ext/oj/reader.c +1 -1
  26. data/ext/oj/rxclass.c +1 -1
  27. data/ext/oj/rxclass.h +1 -1
  28. data/ext/oj/safe.c +230 -0
  29. data/ext/oj/safe.h +79 -0
  30. data/ext/oj/saj.c +15 -10
  31. data/ext/oj/scp.c +3 -6
  32. data/ext/oj/simd.h +219 -0
  33. data/ext/oj/sparse.c +6 -3
  34. data/ext/oj/stream_writer.c +38 -28
  35. data/ext/oj/strict.c +2 -4
  36. data/ext/oj/string_writer.c +54 -22
  37. data/ext/oj/usual.c +57 -35
  38. data/ext/oj/usual.h +1 -0
  39. data/ext/oj/val_stack.c +13 -2
  40. data/ext/oj/wab.c +5 -4
  41. data/lib/oj/mimic.rb +1 -5
  42. data/lib/oj/schandler.rb +5 -4
  43. data/lib/oj/version.rb +1 -1
  44. data/pages/Encoding.md +1 -1
  45. metadata +36 -100
  46. data/test/_test_active.rb +0 -75
  47. data/test/_test_active_mimic.rb +0 -95
  48. data/test/_test_mimic_rails.rb +0 -123
  49. data/test/activerecord/result_test.rb +0 -31
  50. data/test/activesupport6/abstract_unit.rb +0 -44
  51. data/test/activesupport6/decoding_test.rb +0 -133
  52. data/test/activesupport6/encoding_test.rb +0 -507
  53. data/test/activesupport6/encoding_test_cases.rb +0 -98
  54. data/test/activesupport6/test_common.rb +0 -17
  55. data/test/activesupport6/test_helper.rb +0 -163
  56. data/test/activesupport6/time_zone_test_helpers.rb +0 -39
  57. data/test/activesupport7/abstract_unit.rb +0 -49
  58. data/test/activesupport7/decoding_test.rb +0 -125
  59. data/test/activesupport7/encoding_test.rb +0 -486
  60. data/test/activesupport7/encoding_test_cases.rb +0 -104
  61. data/test/activesupport7/time_zone_test_helpers.rb +0 -47
  62. data/test/files.rb +0 -29
  63. data/test/foo.rb +0 -14
  64. data/test/helper.rb +0 -39
  65. data/test/isolated/shared.rb +0 -309
  66. data/test/isolated/test_mimic_after.rb +0 -13
  67. data/test/isolated/test_mimic_alone.rb +0 -12
  68. data/test/isolated/test_mimic_as_json.rb +0 -45
  69. data/test/isolated/test_mimic_before.rb +0 -13
  70. data/test/isolated/test_mimic_define.rb +0 -28
  71. data/test/isolated/test_mimic_rails_after.rb +0 -22
  72. data/test/isolated/test_mimic_rails_before.rb +0 -21
  73. data/test/isolated/test_mimic_redefine.rb +0 -15
  74. data/test/json_gem/json_addition_test.rb +0 -216
  75. data/test/json_gem/json_common_interface_test.rb +0 -155
  76. data/test/json_gem/json_encoding_test.rb +0 -107
  77. data/test/json_gem/json_ext_parser_test.rb +0 -21
  78. data/test/json_gem/json_fixtures_test.rb +0 -36
  79. data/test/json_gem/json_generator_test.rb +0 -407
  80. data/test/json_gem/json_generic_object_test.rb +0 -90
  81. data/test/json_gem/json_parser_test.rb +0 -477
  82. data/test/json_gem/json_string_matching_test.rb +0 -42
  83. data/test/json_gem/test_helper.rb +0 -30
  84. data/test/mem.rb +0 -34
  85. data/test/perf.rb +0 -102
  86. data/test/perf_compat.rb +0 -128
  87. data/test/perf_dump.rb +0 -50
  88. data/test/perf_fast.rb +0 -162
  89. data/test/perf_file.rb +0 -62
  90. data/test/perf_object.rb +0 -134
  91. data/test/perf_once.rb +0 -59
  92. data/test/perf_parser.rb +0 -183
  93. data/test/perf_saj.rb +0 -101
  94. data/test/perf_scp.rb +0 -140
  95. data/test/perf_simple.rb +0 -289
  96. data/test/perf_strict.rb +0 -137
  97. data/test/perf_wab.rb +0 -129
  98. data/test/prec.rb +0 -23
  99. data/test/sample/change.rb +0 -13
  100. data/test/sample/dir.rb +0 -18
  101. data/test/sample/doc.rb +0 -35
  102. data/test/sample/file.rb +0 -47
  103. data/test/sample/group.rb +0 -15
  104. data/test/sample/hasprops.rb +0 -15
  105. data/test/sample/layer.rb +0 -11
  106. data/test/sample/line.rb +0 -20
  107. data/test/sample/oval.rb +0 -10
  108. data/test/sample/rect.rb +0 -9
  109. data/test/sample/shape.rb +0 -34
  110. data/test/sample/text.rb +0 -19
  111. data/test/sample.rb +0 -54
  112. data/test/sample_json.rb +0 -37
  113. data/test/test_compat.rb +0 -567
  114. data/test/test_custom.rb +0 -554
  115. data/test/test_debian.rb +0 -50
  116. data/test/test_fast.rb +0 -526
  117. data/test/test_file.rb +0 -250
  118. data/test/test_gc.rb +0 -60
  119. data/test/test_generate.rb +0 -21
  120. data/test/test_hash.rb +0 -39
  121. data/test/test_integer_range.rb +0 -72
  122. data/test/test_null.rb +0 -376
  123. data/test/test_object.rb +0 -1016
  124. data/test/test_parser.rb +0 -11
  125. data/test/test_parser_debug.rb +0 -27
  126. data/test/test_parser_saj.rb +0 -337
  127. data/test/test_parser_usual.rb +0 -217
  128. data/test/test_rails.rb +0 -35
  129. data/test/test_saj.rb +0 -188
  130. data/test/test_scp.rb +0 -431
  131. data/test/test_strict.rb +0 -431
  132. data/test/test_various.rb +0 -801
  133. data/test/test_wab.rb +0 -311
  134. data/test/test_writer.rb +0 -380
  135. data/test/tests.rb +0 -33
  136. data/test/tests_mimic.rb +0 -23
  137. data/test/tests_mimic_addition.rb +0 -16
data/ext/oj/dump.c CHANGED
@@ -40,7 +40,7 @@ static size_t ascii_friendly_size(const uint8_t *str, size_t len);
40
40
  static const char hex_chars[17] = "0123456789abcdef";
41
41
 
42
42
  // JSON standard except newlines are no escaped
43
- static char newline_friendly_chars[256] = "\
43
+ static char newline_friendly_chars[257] = "\
44
44
  66666666221622666666666666666666\
45
45
  11211111111111111111111111111111\
46
46
  11111111111111111111111111112111\
@@ -51,7 +51,7 @@ static char newline_friendly_chars[256] = "\
51
51
  11111111111111111111111111111111";
52
52
 
53
53
  // JSON standard
54
- static char hibit_friendly_chars[256] = "\
54
+ static char hibit_friendly_chars[257] = "\
55
55
  66666666222622666666666666666666\
56
56
  11211111111111111111111111111111\
57
57
  11111111111111111111111111112111\
@@ -62,7 +62,7 @@ static char hibit_friendly_chars[256] = "\
62
62
  11111111111111111111111111111111";
63
63
 
64
64
  // JSON standard but escape forward slashes `/`
65
- static char slash_friendly_chars[256] = "\
65
+ static char slash_friendly_chars[257] = "\
66
66
  66666666222622666666666666666666\
67
67
  11211111111111121111111111111111\
68
68
  11111111111111111111111111112111\
@@ -74,7 +74,7 @@ static char slash_friendly_chars[256] = "\
74
74
 
75
75
  // High bit set characters are always encoded as unicode. Worse case is 3
76
76
  // bytes per character in the output. That makes this conservative.
77
- static char ascii_friendly_chars[256] = "\
77
+ static char ascii_friendly_chars[257] = "\
78
78
  66666666222622666666666666666666\
79
79
  11211111111111111111111111111111\
80
80
  11111111111111111111111111112111\
@@ -85,7 +85,7 @@ static char ascii_friendly_chars[256] = "\
85
85
  33333333333333333333333333333333";
86
86
 
87
87
  // XSS safe mode
88
- static char xss_friendly_chars[256] = "\
88
+ static char xss_friendly_chars[257] = "\
89
89
  66666666222622666666666666666666\
90
90
  11211161111111121111111111116161\
91
91
  11111111111111111111111111112111\
@@ -96,7 +96,7 @@ static char xss_friendly_chars[256] = "\
96
96
  33333333333333333333333333333333";
97
97
 
98
98
  // JSON XSS combo
99
- static char hixss_friendly_chars[256] = "\
99
+ static char hixss_friendly_chars[257] = "\
100
100
  66666666222622666666666666666666\
101
101
  11211111111111111111111111111111\
102
102
  11111111111111111111111111112111\
@@ -107,7 +107,7 @@ static char hixss_friendly_chars[256] = "\
107
107
  11611111111111111111111111111111";
108
108
 
109
109
  // Rails XSS combo
110
- static char rails_xss_friendly_chars[256] = "\
110
+ static char rails_xss_friendly_chars[257] = "\
111
111
  66666666222622666666666666666666\
112
112
  11211161111111111111111111116161\
113
113
  11111111111111111111111111112111\
@@ -118,7 +118,7 @@ static char rails_xss_friendly_chars[256] = "\
118
118
  11611111111111111111111111111111";
119
119
 
120
120
  // Rails HTML non-escape
121
- static char rails_friendly_chars[256] = "\
121
+ static char rails_friendly_chars[257] = "\
122
122
  66666666222622666666666666666666\
123
123
  11211111111111111111111111111111\
124
124
  11111111111111111111111111112111\
@@ -152,8 +152,146 @@ inline static size_t newline_friendly_size(const uint8_t *str, size_t len) {
152
152
  return calculate_string_size(str, len, newline_friendly_chars);
153
153
  }
154
154
 
155
+ #ifdef HAVE_SIMD_NEON
156
+ inline static uint8x16x4_t load_uint8x16_4(const unsigned char *table) {
157
+ uint8x16x4_t tab;
158
+ tab.val[0] = vld1q_u8(table);
159
+ tab.val[1] = vld1q_u8(table + 16);
160
+ tab.val[2] = vld1q_u8(table + 32);
161
+ tab.val[3] = vld1q_u8(table + 48);
162
+ return tab;
163
+ }
164
+
165
+ static uint8x16x4_t hibit_friendly_chars_neon[2];
166
+ static uint8x16x4_t rails_friendly_chars_neon[2];
167
+ static uint8x16x4_t rails_xss_friendly_chars_neon[4];
168
+
169
+ void initialize_neon(void) {
170
+ // We only need the first 128 bytes of the hibit friendly chars table. Everything above 127 is
171
+ // set to 1. If that ever changes, the code will need to be updated.
172
+ hibit_friendly_chars_neon[0] = load_uint8x16_4((const unsigned char *)hibit_friendly_chars);
173
+ hibit_friendly_chars_neon[1] = load_uint8x16_4((const unsigned char *)hibit_friendly_chars + 64);
174
+
175
+ // rails_friendly_chars is the same as hibit_friendly_chars. Only the first 128 bytes have values
176
+ // that are not '1'. If that ever changes, the code will need to be updated.
177
+ rails_friendly_chars_neon[0] = load_uint8x16_4((const unsigned char *)rails_friendly_chars);
178
+ rails_friendly_chars_neon[1] = load_uint8x16_4((const unsigned char *)rails_friendly_chars + 64);
179
+
180
+ rails_xss_friendly_chars_neon[0] = load_uint8x16_4((const unsigned char *)rails_xss_friendly_chars);
181
+ rails_xss_friendly_chars_neon[1] = load_uint8x16_4((const unsigned char *)rails_xss_friendly_chars + 64);
182
+ rails_xss_friendly_chars_neon[2] = load_uint8x16_4((const unsigned char *)rails_xss_friendly_chars + 128);
183
+ rails_xss_friendly_chars_neon[3] = load_uint8x16_4((const unsigned char *)rails_xss_friendly_chars + 192);
184
+
185
+ // All bytes should be 0 except for those that need more than 1 byte of output. This will allow the
186
+ // code to limit the lookups to the first 128 bytes (values 0 - 127). Bytes above 127 will result
187
+ // in 0 with the vqtbl4q_u8 instruction.
188
+ uint8x16_t one = vdupq_n_u8('1');
189
+ for (int i = 0; i < 2; i++) {
190
+ for (int j = 0; j < 4; j++) {
191
+ hibit_friendly_chars_neon[i].val[j] = vsubq_u8(hibit_friendly_chars_neon[i].val[j], one);
192
+ rails_friendly_chars_neon[i].val[j] = vsubq_u8(rails_friendly_chars_neon[i].val[j], one);
193
+ }
194
+ }
195
+
196
+ for (int i = 0; i < 4; i++) {
197
+ for (int j = 0; j < 4; j++) {
198
+ rails_xss_friendly_chars_neon[i].val[j] = vsubq_u8(rails_xss_friendly_chars_neon[i].val[j], one);
199
+ }
200
+ }
201
+ }
202
+ #endif
203
+
204
+ #ifdef HAVE_SIMD_SSE4_2
205
+
206
+ static __m128i hibit_friendly_chars_sse42[8];
207
+
208
+ // From: https://stackoverflow.com/questions/36998538/fastest-way-to-horizontally-sum-sse-unsigned-byte-vector
209
+ inline static OJ_TARGET_SSE42 uint32_t _mm_sum_epu8(const __m128i v) {
210
+ __m128i vsum = _mm_sad_epu8(v, _mm_setzero_si128());
211
+ return _mm_cvtsi128_si32(vsum) + _mm_extract_epi16(vsum, 4);
212
+ }
213
+
214
+ inline static OJ_TARGET_SSE42 size_t hibit_friendly_size_sse42(const uint8_t *str, size_t len) {
215
+ size_t size = 0;
216
+ size_t i = 0;
217
+
218
+ for (; i + sizeof(__m128i) <= len; i += sizeof(__m128i), str += sizeof(__m128i)) {
219
+ size += sizeof(__m128i);
220
+
221
+ __m128i chunk = _mm_loadu_si128((__m128i *)str);
222
+ __m128i tmp = vector_lookup_sse42(chunk, hibit_friendly_chars_sse42, 8);
223
+ size += _mm_sum_epu8(tmp);
224
+ }
225
+ size_t total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
226
+ return total;
227
+ }
228
+
229
+ void OJ_TARGET_SSE42 initialize_sse42(void) {
230
+ for (int i = 0; i < 8; i++) {
231
+ hibit_friendly_chars_sse42[i] = _mm_sub_epi8(
232
+ _mm_loadu_si128((__m128i *)(hibit_friendly_chars + i * sizeof(__m128i))),
233
+ _mm_set1_epi8('1'));
234
+ }
235
+ }
236
+
237
+ #else
238
+
239
+ #define SIMD_TARGET
240
+
241
+ #endif /* HAVE_SIMD_SSE4_2 */
242
+
155
243
  inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) {
244
+ #ifdef HAVE_SIMD_NEON
245
+ size_t size = 0;
246
+ size_t i = 0;
247
+
248
+ for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
249
+ size += sizeof(uint8x16_t);
250
+
251
+ // See https://lemire.me/blog/2019/07/23/arbitrary-byte-to-byte-maps-using-arm-neon/
252
+ uint8x16_t chunk = vld1q_u8(str);
253
+ uint8x16_t tmp1 = vqtbl4q_u8(hibit_friendly_chars_neon[0], chunk);
254
+ uint8x16_t tmp2 = vqtbl4q_u8(hibit_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
255
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
256
+ uint8_t tmp = vaddvq_u8(result);
257
+ size += tmp;
258
+ }
259
+
260
+ #ifdef HAVE_FAST_MEMCPY
261
+ size_t total = 0;
262
+ if (len - i > 4) {
263
+ size += (len - i);
264
+ unsigned char buf[sizeof(uint8x16_t)];
265
+ memset(buf, ' ', sizeof(buf));
266
+ fast_memcpy16(buf, str, len - i);
267
+
268
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)buf);
269
+ uint8x16_t tmp1 = vqtbl4q_u8(hibit_friendly_chars_neon[0], chunk);
270
+ uint8x16_t tmp2 = vqtbl4q_u8(hibit_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
271
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
272
+ uint8_t tmp = vaddvq_u8(result);
273
+
274
+ size += tmp;
275
+
276
+ total = size;
277
+ } else {
278
+ total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
279
+ }
280
+ #else
281
+ size_t total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
282
+ #endif
283
+
284
+ return total;
285
+ #elif defined(HAVE_SIMD_SSE4_2)
286
+ if (SIMD_Impl == SIMD_SSE42) {
287
+ if (len >= sizeof(__m128i)) {
288
+ return hibit_friendly_size_sse42(str, len);
289
+ }
290
+ }
291
+ return calculate_string_size(str, len, hibit_friendly_chars);
292
+ #else
156
293
  return calculate_string_size(str, len, hibit_friendly_chars);
294
+ #endif
157
295
  }
158
296
 
159
297
  inline static size_t slash_friendly_size(const uint8_t *str, size_t len) {
@@ -183,10 +321,52 @@ inline static size_t hixss_friendly_size(const uint8_t *str, size_t len) {
183
321
  }
184
322
 
185
323
  inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
186
- long size = 0;
187
- size_t i = len;
188
- uint8_t hi = 0;
324
+ long size = 0;
325
+ uint32_t hi = 0;
326
+
327
+ #ifdef HAVE_SIMD_NEON
328
+ size_t i = 0;
329
+
330
+ if (len >= sizeof(uint8x16_t)) {
331
+ uint8x16_t has_some_hibit = vdupq_n_u8(0);
332
+ uint8x16_t hibit = vdupq_n_u8(0x80);
189
333
 
334
+ for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
335
+ size += sizeof(uint8x16_t);
336
+
337
+ uint8x16_t chunk = vld1q_u8(str);
338
+
339
+ // Check to see if any of these bytes have the high bit set.
340
+ has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
341
+
342
+ uint8x16_t tmp1 = vqtbl4q_u8(rails_xss_friendly_chars_neon[0], chunk);
343
+ uint8x16_t tmp2 = vqtbl4q_u8(rails_xss_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
344
+ uint8x16_t tmp3 = vqtbl4q_u8(rails_xss_friendly_chars_neon[2], veorq_u8(chunk, vdupq_n_u8(0x80)));
345
+ uint8x16_t tmp4 = vqtbl4q_u8(rails_xss_friendly_chars_neon[3], veorq_u8(chunk, vdupq_n_u8(0xc0)));
346
+ uint8x16_t result = vorrq_u8(tmp4, vorrq_u8(tmp3, vorrq_u8(tmp1, tmp2)));
347
+ uint8_t tmp = vaddvq_u8(result);
348
+ size += tmp;
349
+ }
350
+
351
+ // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
352
+ hi = vmaxvq_u8(has_some_hibit) != 0;
353
+ }
354
+
355
+ size_t len_remaining = len - i;
356
+
357
+ for (; i < len; str++, i++) {
358
+ size += rails_xss_friendly_chars[*str];
359
+ hi |= *str & 0x80;
360
+ }
361
+
362
+ size -= (len_remaining * ((size_t)'0'));
363
+
364
+ if (0 == hi) {
365
+ return size;
366
+ }
367
+ return -(size);
368
+ #else
369
+ size_t i = len;
190
370
  for (; 0 < i; str++, i--) {
191
371
  size += rails_xss_friendly_chars[*str];
192
372
  hi |= *str & 0x80;
@@ -195,13 +375,65 @@ inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
195
375
  return size - len * (size_t)'0';
196
376
  }
197
377
  return -(size - len * (size_t)'0');
378
+ #endif /* HAVE_SIMD_NEON */
198
379
  }
199
380
 
200
381
  inline static size_t rails_friendly_size(const uint8_t *str, size_t len) {
201
- return calculate_string_size(str, len, rails_friendly_chars);
382
+ long size = 0;
383
+ uint32_t hi = 0;
384
+ #ifdef HAVE_SIMD_NEON
385
+ size_t i = 0;
386
+ long extra = 0;
387
+
388
+ if (len >= sizeof(uint8x16_t)) {
389
+ uint8x16_t has_some_hibit = vdupq_n_u8(0);
390
+ uint8x16_t hibit = vdupq_n_u8(0x80);
391
+
392
+ for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
393
+ size += sizeof(uint8x16_t);
394
+
395
+ // See https://lemire.me/blog/2019/07/23/arbitrary-byte-to-byte-maps-using-arm-neon/
396
+ uint8x16_t chunk = vld1q_u8(str);
397
+
398
+ // Check to see if any of these bytes have the high bit set.
399
+ has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
400
+
401
+ uint8x16_t tmp1 = vqtbl4q_u8(rails_friendly_chars_neon[0], chunk);
402
+ uint8x16_t tmp2 = vqtbl4q_u8(rails_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
403
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
404
+ uint8_t tmp = vaddvq_u8(result);
405
+ size += tmp;
406
+ }
407
+
408
+ // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
409
+ hi = vmaxvq_u8(has_some_hibit) != 0;
410
+ }
411
+
412
+ for (; i < len; str++, i++, extra++) {
413
+ size += rails_friendly_chars[*str];
414
+ hi |= *str & 0x80;
415
+ }
416
+
417
+ size -= (extra * ((size_t)'0'));
418
+
419
+ if (0 == hi) {
420
+ return size;
421
+ }
422
+ return -(size);
423
+ #else
424
+ size_t i = len;
425
+ for (; 0 < i; str++, i--) {
426
+ size += rails_friendly_chars[*str];
427
+ hi |= *str & 0x80;
428
+ }
429
+ if (0 == hi) {
430
+ return size - len * (size_t)'0';
431
+ }
432
+ return -(size - len * (size_t)'0');
433
+ #endif /* HAVE_SIMD_NEON */
202
434
  }
203
435
 
204
- const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, int *lenp) {
436
+ const char *oj_nan_str(VALUE obj, int opt, int mode, bool plus, size_t *lenp) {
205
437
  const char *str = NULL;
206
438
 
207
439
  if (AutoNan == opt) {
@@ -466,7 +698,7 @@ void oj_dump_time(VALUE obj, Out out, int withZone) {
466
698
  void oj_dump_ruby_time(VALUE obj, Out out) {
467
699
  volatile VALUE rstr = oj_safe_string_convert(obj);
468
700
 
469
- oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), 0, 0, out);
701
+ oj_dump_cstr(RSTRING_PTR(rstr), RSTRING_LEN(rstr), 0, 0, out);
470
702
  }
471
703
 
472
704
  void oj_dump_xml_time(VALUE obj, Out out) {
@@ -700,13 +932,13 @@ void oj_dump_str(VALUE obj, int depth, Out out, bool as_ok) {
700
932
  rb_encoding *enc = rb_enc_from_index(idx);
701
933
  obj = rb_str_conv_enc(obj, enc, oj_utf8_encoding);
702
934
  }
703
- oj_dump_cstr(RSTRING_PTR(obj), (int)RSTRING_LEN(obj), 0, 0, out);
935
+ oj_dump_cstr(RSTRING_PTR(obj), RSTRING_LEN(obj), 0, 0, out);
704
936
  }
705
937
 
706
938
  void oj_dump_sym(VALUE obj, int depth, Out out, bool as_ok) {
707
939
  volatile VALUE s = rb_sym2str(obj);
708
940
 
709
- oj_dump_cstr(RSTRING_PTR(s), (int)RSTRING_LEN(s), 0, 0, out);
941
+ oj_dump_cstr(RSTRING_PTR(s), RSTRING_LEN(s), 0, 0, out);
710
942
  }
711
943
 
712
944
  static void debug_raise(const char *orig, size_t cnt, int line) {
@@ -727,8 +959,11 @@ static void debug_raise(const char *orig, size_t cnt, int line) {
727
959
 
728
960
  void oj_dump_raw_json(VALUE obj, int depth, Out out) {
729
961
  if (oj_string_writer_class == rb_obj_class(obj)) {
730
- StrWriter sw = (StrWriter)DATA_PTR(obj);
731
- size_t len = sw->out.cur - sw->out.buf;
962
+ StrWriter sw;
963
+ size_t len;
964
+
965
+ sw = oj_str_writer_unwrap(obj);
966
+ len = sw->out.cur - sw->out.buf;
732
967
 
733
968
  if (0 < len) {
734
969
  len--;
@@ -744,11 +979,158 @@ void oj_dump_raw_json(VALUE obj, int depth, Out out) {
744
979
  }
745
980
  }
746
981
 
982
+ #if defined(__clang__) || defined(__GNUC__)
983
+ #define FORCE_INLINE __attribute__((always_inline))
984
+ #else
985
+ #define FORCE_INLINE
986
+ #endif
987
+
988
+ #ifdef HAVE_SIMD_NEON
989
+ typedef struct _neon_match_result {
990
+ uint8x16_t needs_escape;
991
+ bool has_some_hibit;
992
+ bool do_unicode_validation;
993
+ uint64_t escape_mask;
994
+ } neon_match_result;
995
+
996
+ static inline FORCE_INLINE neon_match_result
997
+ neon_update(const char *str, uint8x16x4_t *cmap_neon, int neon_table_size, bool do_unicode_validation, bool has_hi) {
998
+ neon_match_result result = {.has_some_hibit = false, .do_unicode_validation = false, .escape_mask = 0};
999
+
1000
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)str);
1001
+ uint8x16_t tmp1 = vqtbl4q_u8(cmap_neon[0], chunk);
1002
+ uint8x16_t tmp2 = vqtbl4q_u8(cmap_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
1003
+ result.needs_escape = vorrq_u8(tmp1, tmp2);
1004
+ if (neon_table_size > 2) {
1005
+ uint8x16_t tmp3 = vqtbl4q_u8(cmap_neon[2], veorq_u8(chunk, vdupq_n_u8(0x80)));
1006
+ uint8x16_t tmp4 = vqtbl4q_u8(cmap_neon[3], veorq_u8(chunk, vdupq_n_u8(0xc0)));
1007
+ result.needs_escape = vorrq_u8(result.needs_escape, vorrq_u8(tmp4, tmp3));
1008
+ }
1009
+ if (has_hi && do_unicode_validation) {
1010
+ uint8x16_t has_some_hibit = vandq_u8(chunk, vdupq_n_u8(0x80));
1011
+ result.has_some_hibit = vmaxvq_u8(has_some_hibit) != 0;
1012
+ result.do_unicode_validation = has_hi && do_unicode_validation && result.has_some_hibit;
1013
+ }
1014
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(vmvnq_u8(vceqq_u8(result.needs_escape, vdupq_n_u8(0)))), 4);
1015
+ const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
1016
+ result.escape_mask = mask & 0x8888888888888888ull;
1017
+ return result;
1018
+ }
1019
+
1020
+ #elif defined(HAVE_SIMD_SSE4_2)
1021
+ typedef struct _sse42_match_result {
1022
+ __m128i actions;
1023
+ bool needs_escape;
1024
+ int escape_mask;
1025
+ bool has_some_hibit;
1026
+ bool do_unicode_validation;
1027
+ } sse42_match_result;
1028
+
1029
+ static inline OJ_TARGET_SSE42 sse42_match_result
1030
+ sse42_update(const char *str, __m128i *cmap_sse42, int sse42_tab_size, bool do_unicode_validation, bool has_hi) {
1031
+ sse42_match_result result = {.has_some_hibit = false, .do_unicode_validation = false};
1032
+
1033
+ __m128i chunk = _mm_loadu_si128((__m128i *)str);
1034
+ __m128i actions = vector_lookup_sse42(chunk, cmap_sse42, sse42_tab_size);
1035
+ __m128i needs_escape = _mm_xor_si128(_mm_cmpeq_epi8(actions, _mm_setzero_si128()), _mm_set1_epi8(0xFF));
1036
+ result.actions = _mm_add_epi8(actions, _mm_set1_epi8('1'));
1037
+
1038
+ result.escape_mask = _mm_movemask_epi8(needs_escape);
1039
+ result.needs_escape = result.escape_mask != 0;
1040
+ if (has_hi && do_unicode_validation) {
1041
+ __m128i has_some_hibit = _mm_and_si128(chunk, _mm_set1_epi8(0x80));
1042
+ result.has_some_hibit = _mm_movemask_epi8(has_some_hibit) != 0;
1043
+ result.do_unicode_validation = has_hi && do_unicode_validation && result.has_some_hibit;
1044
+ }
1045
+ return result;
1046
+ }
1047
+
1048
+ #endif /* HAVE_SIMD_NEON */
1049
+
1050
+ static inline FORCE_INLINE const char *process_character(char action,
1051
+ const char *str,
1052
+ const char *end,
1053
+ Out out,
1054
+ const char *orig,
1055
+ bool do_unicode_validation,
1056
+ const char **check_start_) {
1057
+ const char *check_start = *check_start_;
1058
+ switch (action) {
1059
+ case '1':
1060
+ if (do_unicode_validation && check_start <= str) {
1061
+ if (0 != (0x80 & (uint8_t)*str)) {
1062
+ if (0xC0 == (0xC0 & (uint8_t)*str)) {
1063
+ *check_start_ = check_unicode(str, end, orig);
1064
+ } else {
1065
+ raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig));
1066
+ }
1067
+ }
1068
+ }
1069
+ *out->cur++ = *str;
1070
+ break;
1071
+ case '2':
1072
+ *out->cur++ = '\\';
1073
+ switch (*str) {
1074
+ case '\\': *out->cur++ = '\\'; break;
1075
+ case '\b': *out->cur++ = 'b'; break;
1076
+ case '\t': *out->cur++ = 't'; break;
1077
+ case '\n': *out->cur++ = 'n'; break;
1078
+ case '\f': *out->cur++ = 'f'; break;
1079
+ case '\r': *out->cur++ = 'r'; break;
1080
+ default: *out->cur++ = *str; break;
1081
+ }
1082
+ break;
1083
+ case '3': // Unicode
1084
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1085
+ if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1086
+ str = dump_unicode(str, end, out, orig);
1087
+ } else {
1088
+ *check_start_ = check_unicode(str, end, orig);
1089
+ *out->cur++ = *str;
1090
+ }
1091
+ break;
1092
+ }
1093
+ str = dump_unicode(str, end, out, orig);
1094
+ break;
1095
+ case '6': // control characters
1096
+ if (*(uint8_t *)str < 0x80) {
1097
+ if (0 == (uint8_t)*str && out->opts->dump_opts.omit_null_byte) {
1098
+ break;
1099
+ }
1100
+ APPEND_CHARS(out->cur, "\\u00", 4);
1101
+ dump_hex((uint8_t)*str, out);
1102
+ } else {
1103
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1104
+ if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1105
+ str = dump_unicode(str, end, out, orig);
1106
+ } else {
1107
+ *check_start_ = check_unicode(str, end, orig);
1108
+ *out->cur++ = *str;
1109
+ }
1110
+ break;
1111
+ }
1112
+ str = dump_unicode(str, end, out, orig);
1113
+ }
1114
+ break;
1115
+ default: break; // ignore, should never happen if the table is correct
1116
+ }
1117
+
1118
+ return str;
1119
+ }
1120
+
747
1121
  void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
748
- size_t size;
749
- char *cmap;
750
- const char *orig = str;
751
- bool has_hi = false;
1122
+ size_t size;
1123
+ char *cmap;
1124
+ #ifdef HAVE_SIMD_NEON
1125
+ uint8x16x4_t *cmap_neon = NULL;
1126
+ int neon_table_size = 0;
1127
+ #elif defined(HAVE_SIMD_SSE4_2)
1128
+ __m128i *cmap_sse42 = NULL;
1129
+ int sse42_tab_size;
1130
+ #endif /* HAVE_SIMD_NEON */
1131
+ const char *orig = str;
1132
+ bool has_hi = false;
1133
+ bool do_unicode_validation = false;
752
1134
 
753
1135
  switch (out->opts->escape_mode) {
754
1136
  case NLEsc:
@@ -769,28 +1151,55 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
769
1151
  size = xss_friendly_size((uint8_t *)str, cnt);
770
1152
  break;
771
1153
  case JXEsc:
772
- cmap = hixss_friendly_chars;
773
- size = hixss_friendly_size((uint8_t *)str, cnt);
1154
+ cmap = hixss_friendly_chars;
1155
+ size = hixss_friendly_size((uint8_t *)str, cnt);
1156
+ do_unicode_validation = true;
774
1157
  break;
775
1158
  case RailsXEsc: {
776
1159
  long sz;
777
1160
 
778
1161
  cmap = rails_xss_friendly_chars;
779
- sz = rails_xss_friendly_size((uint8_t *)str, cnt);
1162
+ #ifdef HAVE_SIMD_NEON
1163
+ cmap_neon = rails_xss_friendly_chars_neon;
1164
+ neon_table_size = 4;
1165
+ #endif /* HAVE_NEON_SIMD */
1166
+ sz = rails_xss_friendly_size((uint8_t *)str, cnt);
780
1167
  if (sz < 0) {
781
1168
  has_hi = true;
782
1169
  size = (size_t)-sz;
783
1170
  } else {
784
1171
  size = (size_t)sz;
785
1172
  }
1173
+ do_unicode_validation = true;
786
1174
  break;
787
1175
  }
788
- case RailsEsc:
1176
+ case RailsEsc: {
1177
+ long sz;
789
1178
  cmap = rails_friendly_chars;
790
- size = rails_friendly_size((uint8_t *)str, cnt);
1179
+ #ifdef HAVE_SIMD_NEON
1180
+ cmap_neon = rails_friendly_chars_neon;
1181
+ neon_table_size = 2;
1182
+ #endif /* HAVE_NEON_SIMD */
1183
+ sz = rails_friendly_size((uint8_t *)str, cnt);
1184
+ if (sz < 0) {
1185
+ has_hi = true;
1186
+ size = (size_t)-sz;
1187
+ } else {
1188
+ size = (size_t)sz;
1189
+ }
1190
+ do_unicode_validation = true;
791
1191
  break;
1192
+ }
792
1193
  case JSONEsc:
793
- default: cmap = hibit_friendly_chars; size = hibit_friendly_size((uint8_t *)str, cnt);
1194
+ default: cmap = hibit_friendly_chars;
1195
+ #ifdef HAVE_SIMD_NEON
1196
+ cmap_neon = hibit_friendly_chars_neon;
1197
+ neon_table_size = 2;
1198
+ #elif defined(HAVE_SIMD_SSE4_2)
1199
+ cmap_sse42 = hibit_friendly_chars_sse42;
1200
+ sse42_tab_size = 8;
1201
+ #endif /* HAVE_NEON_SIMD */
1202
+ size = hibit_friendly_size((uint8_t *)str, cnt);
794
1203
  }
795
1204
  assure_size(out, size + BUFFER_EXTRA);
796
1205
  *out->cur++ = '"';
@@ -807,7 +1216,16 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
807
1216
  if (is_sym) {
808
1217
  *out->cur++ = ':';
809
1218
  }
1219
+ #ifdef HAVE_FAST_MEMCPY
1220
+ if (cnt <= 16) {
1221
+ fast_memcpy16(out->cur, str, cnt);
1222
+ out->cur += size;
1223
+ } else {
1224
+ APPEND_CHARS(out->cur, str, cnt);
1225
+ }
1226
+ #else
810
1227
  APPEND_CHARS(out->cur, str, cnt);
1228
+ #endif
811
1229
  *out->cur++ = '"';
812
1230
  } else {
813
1231
  const char *end = str + cnt;
@@ -816,74 +1234,168 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
816
1234
  if (is_sym) {
817
1235
  *out->cur++ = ':';
818
1236
  }
819
- for (; str < end; str++) {
820
- switch (cmap[(uint8_t)*str]) {
821
- case '1':
822
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && check_start <= str) {
823
- if (0 != (0x80 & (uint8_t)*str)) {
824
- if (0xC0 == (0xC0 & (uint8_t)*str)) {
825
- check_start = check_unicode(str, end, orig);
826
- } else {
827
- raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig));
828
- }
829
- }
830
- }
831
- *out->cur++ = *str;
832
- break;
833
- case '2':
834
- *out->cur++ = '\\';
835
- switch (*str) {
836
- case '\\': *out->cur++ = '\\'; break;
837
- case '\b': *out->cur++ = 'b'; break;
838
- case '\t': *out->cur++ = 't'; break;
839
- case '\n': *out->cur++ = 'n'; break;
840
- case '\f': *out->cur++ = 'f'; break;
841
- case '\r': *out->cur++ = 'r'; break;
842
- default: *out->cur++ = *str; break;
1237
+
1238
+ #if defined(HAVE_SIMD_NEON) || defined(HAVE_SIMD_SSE4_2)
1239
+
1240
+ #define SEARCH_FLUSH \
1241
+ if (str > cursor) { \
1242
+ APPEND_CHARS(out->cur, cursor, str - cursor); \
1243
+ cursor = str; \
1244
+ }
1245
+
1246
+ const char *chunk_start;
1247
+ const char *chunk_end;
1248
+ const char *cursor = str;
1249
+ char matches[16];
1250
+ #endif /* HAVE_SIMD_NEON || HAVE_SIMD_SSE4_2 */
1251
+
1252
+ #if defined(HAVE_SIMD_NEON)
1253
+ bool use_simd = (cmap_neon != NULL && cnt >= (sizeof(uint8x16_t))) ? true : false;
1254
+ #elif defined(HAVE_SIMD_SSE4_2)
1255
+ bool use_simd = false;
1256
+ if (SIMD_Impl == SIMD_SSE42) {
1257
+ use_simd = (cmap_sse42 != NULL && cnt >= (sizeof(__m128i))) ? true : false;
1258
+ }
1259
+ #endif
1260
+
1261
+ #ifdef HAVE_SIMD_NEON
1262
+
1263
+ #ifdef HAVE_FAST_MEMCPY
1264
+ #define APPEND_CHARS_SMALL(dst, src, length) \
1265
+ fast_memcpy16((dst), (src), (length)); \
1266
+ (dst) += (length);
1267
+ #define MEMCPY16 fast_memcpy16
1268
+ #else
1269
+ #define APPEND_CHARS_SMALL(dst, src, length) APPEND_CHARS(dst, str, length);
1270
+ #define MEMCPY16 memcpy
1271
+ #endif
1272
+
1273
+ if (use_simd) {
1274
+ while (str < end) {
1275
+ const char *chunk_ptr = NULL;
1276
+ if (str + sizeof(uint8x16_t) <= end) {
1277
+ chunk_ptr = str;
1278
+ chunk_start = str;
1279
+ chunk_end = str + sizeof(uint8x16_t);
1280
+ } else if ((end - str) >= SIMD_MINIMUM_THRESHOLD) {
1281
+ memset(out->cur, ' ', sizeof(uint8x16_t));
1282
+ MEMCPY16(out->cur, str, (end - str));
1283
+ chunk_ptr = out->cur;
1284
+ chunk_start = str;
1285
+ chunk_end = end;
1286
+ } else {
1287
+ break;
843
1288
  }
844
- break;
845
- case '3': // Unicode
846
- if (0xe2 == (uint8_t)*str && (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) &&
847
- 2 <= end - str) {
848
- if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
849
- str = dump_unicode(str, end, out, orig);
1289
+ neon_match_result result = neon_update(chunk_ptr,
1290
+ cmap_neon,
1291
+ neon_table_size,
1292
+ do_unicode_validation,
1293
+ has_hi);
1294
+ if ((result.do_unicode_validation) || result.escape_mask != 0) {
1295
+ SEARCH_FLUSH;
1296
+ bool process_each = result.do_unicode_validation;
1297
+ uint8x16_t actions = vaddq_u8(result.needs_escape, vdupq_n_u8('1'));
1298
+ vst1q_u8((unsigned char *)matches, actions);
1299
+ // If no byte in this chunk had the high bit set then we can skip
1300
+ // all of the '1' bytes by directly copying them to the output.
1301
+ if (!process_each) {
1302
+ while (result.escape_mask) {
1303
+ int esc_pos = OJ_CTZ64(result.escape_mask) >> 2;
1304
+ long run_len = esc_pos - (str - chunk_start);
1305
+ if (run_len > 0) {
1306
+ APPEND_CHARS_SMALL(out->cur, str, run_len);
1307
+ str += run_len;
1308
+ }
1309
+ str = process_character(matches[esc_pos],
1310
+ str,
1311
+ end,
1312
+ out,
1313
+ orig,
1314
+ do_unicode_validation,
1315
+ &check_start);
1316
+ str++;
1317
+ result.escape_mask &= result.escape_mask - 1;
1318
+ }
1319
+ if (str < chunk_end) {
1320
+ APPEND_CHARS_SMALL(out->cur, str, chunk_end - str);
1321
+ str = chunk_end;
1322
+ }
850
1323
  } else {
851
- check_start = check_unicode(str, end, orig);
852
- *out->cur++ = *str;
1324
+ while (str < chunk_end) {
1325
+ long match_index = str - chunk_start;
1326
+ str = process_character(matches[match_index],
1327
+ str,
1328
+ end,
1329
+ out,
1330
+ orig,
1331
+ do_unicode_validation,
1332
+ &check_start);
1333
+ str++;
1334
+ }
853
1335
  }
854
- break;
1336
+ cursor = str;
1337
+ continue;
855
1338
  }
856
- str = dump_unicode(str, end, out, orig);
857
- break;
858
- case '6': // control characters
859
- if (*(uint8_t *)str < 0x80) {
860
- if (0 == (uint8_t)*str && out->opts->dump_opts.omit_null_byte) {
1339
+ str = chunk_end;
1340
+ }
1341
+ SEARCH_FLUSH;
1342
+ }
1343
+ #endif
1344
+
1345
+ #ifdef HAVE_SIMD_SSE4_2
1346
+ if (SIMD_Impl == SIMD_SSE42) {
1347
+ if (use_simd) {
1348
+ while (str < end) {
1349
+ const char *chunk_ptr = NULL;
1350
+ if (str + sizeof(__m128i) <= end) {
1351
+ chunk_ptr = str;
1352
+ chunk_start = str;
1353
+ chunk_end = str + sizeof(__m128i);
1354
+ } else if ((end - str) >= SIMD_MINIMUM_THRESHOLD) {
1355
+ memset(out->cur, 'A', sizeof(__m128i));
1356
+ memcpy(out->cur, str, (end - str));
1357
+ chunk_ptr = out->cur;
1358
+ chunk_start = str;
1359
+ chunk_end = end;
1360
+ } else {
861
1361
  break;
862
1362
  }
863
- APPEND_CHARS(out->cur, "\\u00", 4);
864
- dump_hex((uint8_t)*str, out);
865
- } else {
866
- if (0xe2 == (uint8_t)*str &&
867
- (JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 2 <= end - str) {
868
- if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
869
- str = dump_unicode(str, end, out, orig);
870
- } else {
871
- check_start = check_unicode(str, end, orig);
872
- *out->cur++ = *str;
1363
+ sse42_match_result result = sse42_update(chunk_ptr,
1364
+ cmap_sse42,
1365
+ sse42_tab_size,
1366
+ do_unicode_validation,
1367
+ has_hi);
1368
+ if ((result.do_unicode_validation) || result.needs_escape) {
1369
+ SEARCH_FLUSH;
1370
+ _mm_storeu_si128((__m128i *)matches, result.actions);
1371
+ while (str < chunk_end) {
1372
+ long match_index = str - chunk_start;
1373
+ str = process_character(matches[match_index],
1374
+ str,
1375
+ end,
1376
+ out,
1377
+ orig,
1378
+ do_unicode_validation,
1379
+ &check_start);
1380
+ str++;
873
1381
  }
874
- break;
1382
+ cursor = str;
1383
+ continue;
875
1384
  }
876
- str = dump_unicode(str, end, out, orig);
1385
+ str = chunk_end;
877
1386
  }
878
- break;
879
- default: break; // ignore, should never happen if the table is correct
1387
+ SEARCH_FLUSH;
880
1388
  }
881
1389
  }
1390
+ #endif /* HAVE_SIMD_SSE4_2 */
1391
+
1392
+ for (; str < end; str++) {
1393
+ str = process_character(cmap[(uint8_t)*str], str, end, out, orig, do_unicode_validation, &check_start);
1394
+ }
882
1395
  *out->cur++ = '"';
883
1396
  }
884
- if ((JXEsc == out->opts->escape_mode || RailsXEsc == out->opts->escape_mode) && 0 < str - orig &&
885
- 0 != (0x80 & *(str - 1))) {
886
- uint8_t c = (uint8_t) * (str - 1);
1397
+ if (do_unicode_validation && 0 < str - orig && 0 != (0x80 & *(str - 1))) {
1398
+ uint8_t c = (uint8_t)*(str - 1);
887
1399
  int i;
888
1400
  int scnt = (int)(str - orig);
889
1401
 
@@ -934,7 +1446,7 @@ void oj_dump_class(VALUE obj, int depth, Out out, bool as_ok) {
934
1446
  void oj_dump_obj_to_s(VALUE obj, Out out) {
935
1447
  volatile VALUE rstr = oj_safe_string_convert(obj);
936
1448
 
937
- oj_dump_cstr(RSTRING_PTR(rstr), (int)RSTRING_LEN(rstr), 0, 0, out);
1449
+ oj_dump_cstr(RSTRING_PTR(rstr), RSTRING_LEN(rstr), 0, 0, out);
938
1450
  }
939
1451
 
940
1452
  void oj_dump_raw(const char *str, size_t cnt, Out out) {
@@ -1069,7 +1581,7 @@ void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) {
1069
1581
 
1070
1582
  void oj_dump_bignum(VALUE obj, int depth, Out out, bool as_ok) {
1071
1583
  volatile VALUE rs = rb_big2str(obj, 10);
1072
- int cnt = (int)RSTRING_LEN(rs);
1584
+ size_t cnt = RSTRING_LEN(rs);
1073
1585
  bool dump_as_string = false;
1074
1586
 
1075
1587
  if (out->opts->int_range_max != 0 || out->opts->int_range_min != 0) { // Bignum cannot be inside of Fixnum range
@@ -1091,7 +1603,7 @@ void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) {
1091
1603
  char buf[64];
1092
1604
  char *b;
1093
1605
  double d = rb_num2dbl(obj);
1094
- int cnt = 0;
1606
+ size_t cnt = 0;
1095
1607
 
1096
1608
  if (0.0 == d) {
1097
1609
  b = buf;
@@ -1202,7 +1714,7 @@ void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) {
1202
1714
  } else if (0 == out->opts->float_prec) {
1203
1715
  volatile VALUE rstr = oj_safe_string_convert(obj);
1204
1716
 
1205
- cnt = (int)RSTRING_LEN(rstr);
1717
+ cnt = RSTRING_LEN(rstr);
1206
1718
  if ((int)sizeof(buf) <= cnt) {
1207
1719
  cnt = sizeof(buf) - 1;
1208
1720
  }
@@ -1216,8 +1728,8 @@ void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) {
1216
1728
  *out->cur = '\0';
1217
1729
  }
1218
1730
 
1219
- int oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format) {
1220
- int cnt = snprintf(buf, blen, format, d);
1731
+ size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format) {
1732
+ size_t cnt = snprintf(buf, blen, format, d);
1221
1733
 
1222
1734
  // Round off issues at 16 significant digits so check for obvious ones of
1223
1735
  // 0001 and 9999.
@@ -1225,7 +1737,29 @@ int oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char
1225
1737
  volatile VALUE rstr = oj_safe_string_convert(obj);
1226
1738
 
1227
1739
  strcpy(buf, RSTRING_PTR(rstr));
1228
- cnt = (int)RSTRING_LEN(rstr);
1740
+ cnt = RSTRING_LEN(rstr);
1229
1741
  }
1230
1742
  return cnt;
1231
1743
  }
1744
+
1745
+ bool oj_key_skip(VALUE key, const char *only, const char *except) {
1746
+ const char *skey;
1747
+
1748
+ switch (rb_type(key)) {
1749
+ case RUBY_T_STRING: skey = StringValueCStr(key); break;
1750
+ case RUBY_T_SYMBOL: skey = rb_id2name(rb_sym2id(key)); break;
1751
+ default: skey = NULL; break;
1752
+ }
1753
+ if (NULL != skey && '\0' != *skey) {
1754
+ size_t size = strlen(skey);
1755
+ char *buf = alloca(size + 3);
1756
+
1757
+ buf[0] = ':';
1758
+ strcpy(buf + 1, skey);
1759
+ buf[size + 1] = ':';
1760
+ buf[size + 2] = '\0';
1761
+
1762
+ return ((NULL != only && NULL == strstr(only, buf)) || (NULL != except && NULL != strstr(except, buf)));
1763
+ }
1764
+ return NULL != only;
1765
+ }