oj 3.16.3 → 3.17.4

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -0
  3. data/README.md +0 -16
  4. data/ext/oj/compat.c +3 -3
  5. data/ext/oj/custom.c +17 -11
  6. data/ext/oj/dump.c +619 -88
  7. data/ext/oj/dump.h +9 -2
  8. data/ext/oj/dump_compat.c +16 -9
  9. data/ext/oj/dump_leaf.c +1 -1
  10. data/ext/oj/dump_object.c +52 -27
  11. data/ext/oj/dump_strict.c +20 -14
  12. data/ext/oj/extconf.rb +11 -9
  13. data/ext/oj/fast.c +90 -65
  14. data/ext/oj/intern.c +1 -1
  15. data/ext/oj/mimic_json.c +14 -8
  16. data/ext/oj/object.c +18 -12
  17. data/ext/oj/oj.c +426 -11
  18. data/ext/oj/oj.h +60 -52
  19. data/ext/oj/parse.c +252 -48
  20. data/ext/oj/parse.h +0 -1
  21. data/ext/oj/parser.c +179 -47
  22. data/ext/oj/parser.h +4 -2
  23. data/ext/oj/rails.c +57 -36
  24. data/ext/oj/reader.c +17 -5
  25. data/ext/oj/rxclass.c +17 -1
  26. data/ext/oj/rxclass.h +2 -1
  27. data/ext/oj/safe.c +230 -0
  28. data/ext/oj/safe.h +79 -0
  29. data/ext/oj/saj.c +55 -20
  30. data/ext/oj/scp.c +3 -6
  31. data/ext/oj/simd.h +219 -0
  32. data/ext/oj/sparse.c +15 -3
  33. data/ext/oj/stream_writer.c +19 -14
  34. data/ext/oj/strict.c +2 -4
  35. data/ext/oj/string_writer.c +26 -14
  36. data/ext/oj/usual.c +37 -35
  37. data/ext/oj/wab.c +5 -4
  38. data/lib/oj/mimic.rb +1 -5
  39. data/lib/oj/schandler.rb +5 -4
  40. data/lib/oj/version.rb +1 -1
  41. data/pages/Encoding.md +1 -1
  42. metadata +22 -100
  43. data/test/_test_active.rb +0 -75
  44. data/test/_test_active_mimic.rb +0 -95
  45. data/test/_test_mimic_rails.rb +0 -123
  46. data/test/activerecord/result_test.rb +0 -31
  47. data/test/activesupport6/abstract_unit.rb +0 -44
  48. data/test/activesupport6/decoding_test.rb +0 -133
  49. data/test/activesupport6/encoding_test.rb +0 -507
  50. data/test/activesupport6/encoding_test_cases.rb +0 -98
  51. data/test/activesupport6/test_common.rb +0 -17
  52. data/test/activesupport6/test_helper.rb +0 -163
  53. data/test/activesupport6/time_zone_test_helpers.rb +0 -39
  54. data/test/activesupport7/abstract_unit.rb +0 -49
  55. data/test/activesupport7/decoding_test.rb +0 -125
  56. data/test/activesupport7/encoding_test.rb +0 -486
  57. data/test/activesupport7/encoding_test_cases.rb +0 -104
  58. data/test/activesupport7/time_zone_test_helpers.rb +0 -47
  59. data/test/files.rb +0 -29
  60. data/test/foo.rb +0 -14
  61. data/test/helper.rb +0 -39
  62. data/test/isolated/shared.rb +0 -309
  63. data/test/isolated/test_mimic_after.rb +0 -13
  64. data/test/isolated/test_mimic_alone.rb +0 -12
  65. data/test/isolated/test_mimic_as_json.rb +0 -45
  66. data/test/isolated/test_mimic_before.rb +0 -13
  67. data/test/isolated/test_mimic_define.rb +0 -28
  68. data/test/isolated/test_mimic_rails_after.rb +0 -22
  69. data/test/isolated/test_mimic_rails_before.rb +0 -21
  70. data/test/isolated/test_mimic_redefine.rb +0 -15
  71. data/test/json_gem/json_addition_test.rb +0 -216
  72. data/test/json_gem/json_common_interface_test.rb +0 -155
  73. data/test/json_gem/json_encoding_test.rb +0 -107
  74. data/test/json_gem/json_ext_parser_test.rb +0 -21
  75. data/test/json_gem/json_fixtures_test.rb +0 -36
  76. data/test/json_gem/json_generator_test.rb +0 -413
  77. data/test/json_gem/json_generic_object_test.rb +0 -90
  78. data/test/json_gem/json_parser_test.rb +0 -477
  79. data/test/json_gem/json_string_matching_test.rb +0 -42
  80. data/test/json_gem/test_helper.rb +0 -30
  81. data/test/mem.rb +0 -34
  82. data/test/perf.rb +0 -102
  83. data/test/perf_compat.rb +0 -128
  84. data/test/perf_dump.rb +0 -50
  85. data/test/perf_fast.rb +0 -162
  86. data/test/perf_file.rb +0 -62
  87. data/test/perf_object.rb +0 -134
  88. data/test/perf_once.rb +0 -59
  89. data/test/perf_parser.rb +0 -183
  90. data/test/perf_saj.rb +0 -101
  91. data/test/perf_scp.rb +0 -140
  92. data/test/perf_simple.rb +0 -289
  93. data/test/perf_strict.rb +0 -137
  94. data/test/perf_wab.rb +0 -129
  95. data/test/prec.rb +0 -23
  96. data/test/sample/change.rb +0 -13
  97. data/test/sample/dir.rb +0 -18
  98. data/test/sample/doc.rb +0 -35
  99. data/test/sample/file.rb +0 -47
  100. data/test/sample/group.rb +0 -15
  101. data/test/sample/hasprops.rb +0 -15
  102. data/test/sample/layer.rb +0 -11
  103. data/test/sample/line.rb +0 -20
  104. data/test/sample/oval.rb +0 -10
  105. data/test/sample/rect.rb +0 -9
  106. data/test/sample/shape.rb +0 -34
  107. data/test/sample/text.rb +0 -19
  108. data/test/sample.rb +0 -54
  109. data/test/sample_json.rb +0 -37
  110. data/test/test_compat.rb +0 -567
  111. data/test/test_custom.rb +0 -555
  112. data/test/test_debian.rb +0 -50
  113. data/test/test_fast.rb +0 -526
  114. data/test/test_file.rb +0 -250
  115. data/test/test_gc.rb +0 -60
  116. data/test/test_generate.rb +0 -21
  117. data/test/test_hash.rb +0 -39
  118. data/test/test_integer_range.rb +0 -72
  119. data/test/test_null.rb +0 -376
  120. data/test/test_object.rb +0 -1030
  121. data/test/test_parser.rb +0 -11
  122. data/test/test_parser_debug.rb +0 -27
  123. data/test/test_parser_saj.rb +0 -337
  124. data/test/test_parser_usual.rb +0 -227
  125. data/test/test_rails.rb +0 -35
  126. data/test/test_saj.rb +0 -188
  127. data/test/test_scp.rb +0 -431
  128. data/test/test_strict.rb +0 -441
  129. data/test/test_various.rb +0 -801
  130. data/test/test_wab.rb +0 -311
  131. data/test/test_writer.rb +0 -380
  132. data/test/tests.rb +0 -33
  133. data/test/tests_mimic.rb +0 -23
  134. data/test/tests_mimic_addition.rb +0 -16
data/ext/oj/parser.c CHANGED
@@ -5,6 +5,7 @@
5
5
  #include <fcntl.h>
6
6
 
7
7
  #include "oj.h"
8
+ #include "simd.h"
8
9
 
9
10
  #define DEBUG 0
10
11
 
@@ -75,7 +76,7 @@ enum {
75
76
 
76
77
  /*
77
78
  0123456789abcdef0123456789abcdef */
78
- static const char value_map[257] = "\
79
+ static const char value_map[258] = "\
79
80
  X........ab..a..................\
80
81
  a.i..........f..ghhhhhhhhh......\
81
82
  ...........................k.m..\
@@ -85,7 +86,7 @@ a.i..........f..ghhhhhhhhh......\
85
86
  ................................\
86
87
  ................................v";
87
88
 
88
- static const char null_map[257] = "\
89
+ static const char null_map[258] = "\
89
90
  ................................\
90
91
  ............o...................\
91
92
  ................................\
@@ -95,7 +96,7 @@ static const char null_map[257] = "\
95
96
  ................................\
96
97
  ................................N";
97
98
 
98
- static const char true_map[257] = "\
99
+ static const char true_map[258] = "\
99
100
  ................................\
100
101
  ............o...................\
101
102
  ................................\
@@ -105,7 +106,7 @@ static const char true_map[257] = "\
105
106
  ................................\
106
107
  ................................T";
107
108
 
108
- static const char false_map[257] = "\
109
+ static const char false_map[258] = "\
109
110
  ................................\
110
111
  ............o...................\
111
112
  ................................\
@@ -115,7 +116,7 @@ static const char false_map[257] = "\
115
116
  ................................\
116
117
  ................................F";
117
118
 
118
- static const char comma_map[257] = "\
119
+ static const char comma_map[258] = "\
119
120
  .........ab..a..................\
120
121
  a.i..........f..ghhhhhhhhh......\
121
122
  ...........................k....\
@@ -125,7 +126,7 @@ a.i..........f..ghhhhhhhhh......\
125
126
  ................................\
126
127
  ................................,";
127
128
 
128
- static const char after_map[257] = "\
129
+ static const char after_map[258] = "\
129
130
  X........ab..a..................\
130
131
  a...........o...................\
131
132
  .............................m..\
@@ -135,7 +136,7 @@ a...........o...................\
135
136
  ................................\
136
137
  ................................a";
137
138
 
138
- static const char key1_map[257] = "\
139
+ static const char key1_map[258] = "\
139
140
  .........ab..a..................\
140
141
  a.p.............................\
141
142
  ................................\
@@ -145,7 +146,7 @@ a.p.............................\
145
146
  ................................\
146
147
  ................................K";
147
148
 
148
- static const char key_map[257] = "\
149
+ static const char key_map[258] = "\
149
150
  .........ab..a..................\
150
151
  a.p.............................\
151
152
  ................................\
@@ -155,7 +156,7 @@ a.p.............................\
155
156
  ................................\
156
157
  ................................k";
157
158
 
158
- static const char colon_map[257] = "\
159
+ static const char colon_map[258] = "\
159
160
  .........ab..a..................\
160
161
  a.........................q.....\
161
162
  ................................\
@@ -165,7 +166,7 @@ a.........................q.....\
165
166
  ................................\
166
167
  ................................:";
167
168
 
168
- static const char neg_map[257] = "\
169
+ static const char neg_map[258] = "\
169
170
  ................................\
170
171
  ................O---------......\
171
172
  ................................\
@@ -175,7 +176,7 @@ static const char neg_map[257] = "\
175
176
  ................................\
176
177
  ................................-";
177
178
 
178
- static const char zero_map[257] = "\
179
+ static const char zero_map[258] = "\
179
180
  .........rs..r..................\
180
181
  r...........u.t.................\
181
182
  .............................H..\
@@ -185,7 +186,7 @@ r...........u.t.................\
185
186
  ................................\
186
187
  ................................0";
187
188
 
188
- static const char digit_map[257] = "\
189
+ static const char digit_map[258] = "\
189
190
  .........rs..r..................\
190
191
  r...........u.t.NNNNNNNNNN......\
191
192
  .....w.......................H..\
@@ -195,7 +196,7 @@ r...........u.t.NNNNNNNNNN......\
195
196
  ................................\
196
197
  ................................d";
197
198
 
198
- static const char dot_map[257] = "\
199
+ static const char dot_map[258] = "\
199
200
  ................................\
200
201
  ................vvvvvvvvvv......\
201
202
  ................................\
@@ -205,7 +206,7 @@ static const char dot_map[257] = "\
205
206
  ................................\
206
207
  .................................";
207
208
 
208
- static const char frac_map[257] = "\
209
+ static const char frac_map[258] = "\
209
210
  .........rs..r..................\
210
211
  r...........u...vvvvvvvvvv......\
211
212
  .....w.......................H..\
@@ -215,7 +216,7 @@ r...........u...vvvvvvvvvv......\
215
216
  ................................\
216
217
  ................................f";
217
218
 
218
- static const char exp_sign_map[257] = "\
219
+ static const char exp_sign_map[258] = "\
219
220
  ................................\
220
221
  ...........x.x..yyyyyyyyyy......\
221
222
  ................................\
@@ -225,7 +226,7 @@ static const char exp_sign_map[257] = "\
225
226
  ................................\
226
227
  ................................x";
227
228
 
228
- static const char exp_zero_map[257] = "\
229
+ static const char exp_zero_map[258] = "\
229
230
  ................................\
230
231
  ................yyyyyyyyyy......\
231
232
  ................................\
@@ -235,7 +236,7 @@ static const char exp_zero_map[257] = "\
235
236
  ................................\
236
237
  ................................z";
237
238
 
238
- static const char exp_map[257] = "\
239
+ static const char exp_map[258] = "\
239
240
  .........rs..r..................\
240
241
  r...........u...yyyyyyyyyy......\
241
242
  .............................H..\
@@ -245,7 +246,7 @@ r...........u...yyyyyyyyyy......\
245
246
  ................................\
246
247
  ................................X";
247
248
 
248
- static const char big_digit_map[257] = "\
249
+ static const char big_digit_map[258] = "\
249
250
  .........rs..r..................\
250
251
  r...........u.D.CCCCCCCCCC......\
251
252
  .....J.......................H..\
@@ -255,7 +256,7 @@ r...........u.D.CCCCCCCCCC......\
255
256
  ................................\
256
257
  ................................D";
257
258
 
258
- static const char big_dot_map[257] = "\
259
+ static const char big_dot_map[258] = "\
259
260
  ................................\
260
261
  ................IIIIIIIIII......\
261
262
  ................................\
@@ -265,7 +266,7 @@ static const char big_dot_map[257] = "\
265
266
  ................................\
266
267
  ................................o";
267
268
 
268
- static const char big_frac_map[257] = "\
269
+ static const char big_frac_map[258] = "\
269
270
  .........rs..r..................\
270
271
  r...........u...IIIIIIIIII......\
271
272
  .....J.......................H..\
@@ -275,7 +276,7 @@ r...........u...IIIIIIIIII......\
275
276
  ................................\
276
277
  ................................g";
277
278
 
278
- static const char big_exp_sign_map[257] = "\
279
+ static const char big_exp_sign_map[258] = "\
279
280
  ................................\
280
281
  ...........K.K..LLLLLLLLLL......\
281
282
  ................................\
@@ -285,7 +286,7 @@ static const char big_exp_sign_map[257] = "\
285
286
  ................................\
286
287
  ................................B";
287
288
 
288
- static const char big_exp_zero_map[257] = "\
289
+ static const char big_exp_zero_map[258] = "\
289
290
  ................................\
290
291
  ................LLLLLLLLLL......\
291
292
  ................................\
@@ -295,7 +296,7 @@ static const char big_exp_zero_map[257] = "\
295
296
  ................................\
296
297
  ................................Z";
297
298
 
298
- static const char big_exp_map[257] = "\
299
+ static const char big_exp_map[258] = "\
299
300
  .........rs..r..................\
300
301
  r...........u...LLLLLLLLLL......\
301
302
  .............................H..\
@@ -305,7 +306,7 @@ r...........u...LLLLLLLLLL......\
305
306
  ................................\
306
307
  ................................Y";
307
308
 
308
- static const char string_map[257] = "\
309
+ static const char string_map[258] = "\
309
310
  ................................\
310
311
  RRzRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\
311
312
  RRRRRRRRRRRRRRRRRRRRRRRRRRRRARRR\
@@ -315,7 +316,7 @@ RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR\
315
316
  MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\
316
317
  PPPPPPPPPPPPPPPPQQQQQQQQ........s";
317
318
 
318
- static const char esc_map[257] = "\
319
+ static const char esc_map[258] = "\
319
320
  ................................\
320
321
  ..B............B................\
321
322
  ............................B...\
@@ -325,7 +326,7 @@ static const char esc_map[257] = "\
325
326
  ................................\
326
327
  ................................~";
327
328
 
328
- static const char esc_byte_map[257] = "\
329
+ static const char esc_byte_map[258] = "\
329
330
  ................................\
330
331
  ..\"............/................\
331
332
  ............................\\...\
@@ -335,7 +336,7 @@ static const char esc_byte_map[257] = "\
335
336
  ................................\
336
337
  ................................b";
337
338
 
338
- static const char u_map[257] = "\
339
+ static const char u_map[258] = "\
339
340
  ................................\
340
341
  ................EEEEEEEEEE......\
341
342
  .EEEEEE.........................\
@@ -345,7 +346,7 @@ static const char u_map[257] = "\
345
346
  ................................\
346
347
  ................................u";
347
348
 
348
- static const char utf_map[257] = "\
349
+ static const char utf_map[258] = "\
349
350
  ................................\
350
351
  ................................\
351
352
  ................................\
@@ -355,7 +356,7 @@ SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS\
355
356
  ................................\
356
357
  ................................8";
357
358
 
358
- static const char space_map[257] = "\
359
+ static const char space_map[258] = "\
359
360
  .........ab..a..................\
360
361
  a...............................\
361
362
  ................................\
@@ -365,7 +366,7 @@ a...............................\
365
366
  ................................\
366
367
  ................................S";
367
368
 
368
- static const char trail_map[257] = "\
369
+ static const char trail_map[258] = "\
369
370
  .........ab..a..................\
370
371
  a...............................\
371
372
  ................................\
@@ -375,7 +376,7 @@ a...............................\
375
376
  ................................\
376
377
  ................................R";
377
378
 
378
- static const byte hex_map[256] = "\
379
+ static const byte hex_map[257] = "\
379
380
  ................................\
380
381
  ................\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09......\
381
382
  .\x0a\x0b\x0c\x0d\x0e\x0f.........................\
@@ -594,9 +595,64 @@ static void big_change(ojParser p) {
594
595
  }
595
596
  }
596
597
 
597
- static void parse(ojParser p, const byte *json) {
598
+ // Scan forward over string content, returning the first byte that is not
599
+ // STR_OK in string_map. This is a pure drop-in for the scalar loop
600
+ //
601
+ // for (; STR_OK == string_map[*b]; b++) {}
602
+ //
603
+ // and must return the exact same stop position. The stop set (bytes whose
604
+ // string_map class is not 'R'/STR_OK) is:
605
+ //
606
+ // * control bytes 0x00..0x1F (class '.', also stops at the NUL terminator)
607
+ // * the quote 0x22 '"' (class 'z')
608
+ // * the backslash 0x5C '\' (class 'A')
609
+ // * every high byte 0x80..0xFF (UTF-8 lead/continuation, classes M/P/Q/'.')
610
+ //
611
+ // Note this differs from parse.c's string_scan_neon, which stops only on
612
+ // \0 \\ " -- parser.c hands multi-byte UTF-8 to its state machine, so the
613
+ // scanner must stop on the high bytes too. The predictor below is derived from
614
+ // string_map, not copied from parse.c.
615
+ //
616
+ // The NEON path only loads a full 16-byte vector when [b, b+16) stays within
617
+ // [., end), so it never reads past the string's allocation; the sub-16-byte
618
+ // tail (and the whole scan on non-NEON builds) uses the scalar loop, which
619
+ // stops naturally at the guaranteed NUL terminator.
620
+ static inline const byte *oj_scan_str_simd(const byte *b, const byte *end) {
621
+ #ifdef HAVE_SIMD_NEON
622
+ if (SIMD_NEON == SIMD_Impl) {
623
+ const uint8x16_t quote = vdupq_n_u8('"');
624
+ const uint8x16_t bslash = vdupq_n_u8('\\');
625
+ const uint8x16_t space = vdupq_n_u8(0x20);
626
+ const uint8x16_t high = vdupq_n_u8(0x80);
627
+
628
+ while (b + sizeof(uint8x16_t) <= end) {
629
+ const uint8x16_t chunk = vld1q_u8((const uint8_t *)b);
630
+ // special lane == 0xFF for any byte that stops the scan.
631
+ const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(chunk, quote), vceqq_u8(chunk, bslash)),
632
+ vorrq_u8(vcltq_u8(chunk, space), vcgeq_u8(chunk, high)));
633
+ // Reduce to a 64-bit mask with 4 bits per lane (same idiom as
634
+ // parse.c's string_scan_neon) and locate the first set lane.
635
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(special), 4);
636
+ uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
637
+ if (0 != mask) {
638
+ mask &= 0x8888888888888888ull;
639
+ return b + (OJ_CTZ64(mask) >> 2);
640
+ }
641
+ b += sizeof(uint8x16_t);
642
+ }
643
+ }
644
+ #else
645
+ (void)end;
646
+ #endif
647
+ for (; STR_OK == string_map[*b]; b++) {
648
+ }
649
+ return b;
650
+ }
651
+
652
+ static void parse(ojParser p, const byte *json, size_t len, bool more) {
598
653
  const byte *start;
599
- const byte *b = json;
654
+ const byte *b = json;
655
+ const byte *end = json + len;
600
656
  int i;
601
657
 
602
658
  p->line = 1;
@@ -629,12 +685,14 @@ static void parse(ojParser p, const byte *json) {
629
685
  b++;
630
686
  p->key.tail = p->key.head;
631
687
  start = b;
632
- for (; STR_OK == string_map[*b]; b++) {
633
- }
688
+ b = oj_scan_str_simd(b, end);
634
689
  buf_append_string(&p->key, (const char *)start, b - start);
635
690
  if ('"' == *b) {
636
691
  p->map = colon_map;
637
692
  break;
693
+ } else if ('\0' == *b && !more) {
694
+ parse_error(p, "quoted string not terminated");
695
+ break;
638
696
  }
639
697
  b--;
640
698
  p->map = string_map;
@@ -651,14 +709,16 @@ static void parse(ojParser p, const byte *json) {
651
709
  b++;
652
710
  start = b;
653
711
  p->buf.tail = p->buf.head;
654
- for (; STR_OK == string_map[*b]; b++) {
655
- }
712
+ b = oj_scan_str_simd(b, end);
656
713
  buf_append_string(&p->buf, (const char *)start, b - start);
657
714
  if ('"' == *b) {
658
715
  p->cur = b - json;
659
716
  p->funcs[p->stack[p->depth]].add_str(p);
660
717
  p->map = (0 == p->depth) ? value_map : after_map;
661
718
  break;
719
+ } else if ('\0' == *b && !more) {
720
+ parse_error(p, "quoted string not terminated");
721
+ break;
662
722
  }
663
723
  b--;
664
724
  p->map = string_map;
@@ -668,6 +728,10 @@ static void parse(ojParser p, const byte *json) {
668
728
  p->cur = b - json;
669
729
  p->funcs[p->stack[p->depth]].open_object(p);
670
730
  p->depth++;
731
+ if ((int)sizeof(p->stack) <= p->depth) {
732
+ parse_error(p, "too deeply nested");
733
+ break;
734
+ }
671
735
  p->stack[p->depth] = OBJECT_FUN;
672
736
  p->map = key1_map;
673
737
  break;
@@ -690,6 +754,10 @@ static void parse(ojParser p, const byte *json) {
690
754
  p->cur = b - json;
691
755
  p->funcs[p->stack[p->depth]].open_array(p);
692
756
  p->depth++;
757
+ if ((int)sizeof(p->stack) <= p->depth) {
758
+ parse_error(p, "too deeply nested");
759
+ break;
760
+ }
693
761
  p->stack[p->depth] = ARRAY_FUN;
694
762
  p->map = value_map;
695
763
  break;
@@ -891,8 +959,7 @@ static void parse(ojParser p, const byte *json) {
891
959
  break;
892
960
  case STR_OK:
893
961
  start = b;
894
- for (; STR_OK == string_map[*b]; b++) {
895
- }
962
+ b = oj_scan_str_simd(b, end);
896
963
  if (':' == p->next_map[256]) {
897
964
  buf_append_string(&p->key, (const char *)start, b - start);
898
965
  } else {
@@ -1114,9 +1181,6 @@ static void parse(ojParser p, const byte *json) {
1114
1181
  p->map = trail_map;
1115
1182
  }
1116
1183
  }
1117
- if (0 < p->depth) {
1118
- parse_error(p, "parse error, not closed");
1119
- }
1120
1184
  if (0 == p->depth) {
1121
1185
  switch (p->map[256]) {
1122
1186
  case '0':
@@ -1164,7 +1228,7 @@ static void parser_mark(void *ptr) {
1164
1228
  }
1165
1229
  }
1166
1230
 
1167
- static const rb_data_type_t oj_parser_type = {
1231
+ const rb_data_type_t oj_parser_type = {
1168
1232
  "Oj/parser",
1169
1233
  {
1170
1234
  parser_mark,
@@ -1179,6 +1243,8 @@ extern void oj_set_parser_validator(ojParser p);
1179
1243
  extern void oj_set_parser_saj(ojParser p);
1180
1244
  extern void oj_set_parser_usual(ojParser p);
1181
1245
  extern void oj_set_parser_debug(ojParser p);
1246
+ extern void oj_set_parser_safe(ojParser p, VALUE options);
1247
+ extern void oj_safe_init(VALUE parser_class);
1182
1248
 
1183
1249
  static int opt_cb(VALUE rkey, VALUE value, VALUE ptr) {
1184
1250
  ojParser p = (ojParser)ptr;
@@ -1357,6 +1423,34 @@ static VALUE parser_missing(int argc, VALUE *argv, VALUE self) {
1357
1423
  return p->option(p, key, rv);
1358
1424
  }
1359
1425
 
1426
+ static void validate_primitives_are_complete(ojParser p) {
1427
+ if (0 >= p->ri) {
1428
+ return;
1429
+ }
1430
+
1431
+ switch (p->map[256]) {
1432
+ case 'N': parse_error(p, "expected null"); break;
1433
+ case 'F': parse_error(p, "expected false"); break;
1434
+ case 'T': parse_error(p, "expected true"); break;
1435
+ }
1436
+ }
1437
+
1438
+ static void validate_non_primitives_are_complete(ojParser p) {
1439
+ if (0 >= p->depth) {
1440
+ return;
1441
+ }
1442
+ if (OBJECT_FUN == p->stack[p->depth]) {
1443
+ parse_error(p, "Object is not closed");
1444
+ } else {
1445
+ parse_error(p, "Array is not closed");
1446
+ }
1447
+ }
1448
+
1449
+ static void validate_document_end(ojParser p) {
1450
+ validate_primitives_are_complete(p);
1451
+ validate_non_primitives_are_complete(p);
1452
+ }
1453
+
1360
1454
  /* Document-method: parse(json)
1361
1455
  * call-seq: parse(json)
1362
1456
  *
@@ -1366,13 +1460,20 @@ static VALUE parser_missing(int argc, VALUE *argv, VALUE self) {
1366
1460
  */
1367
1461
  static VALUE parser_parse(VALUE self, VALUE json) {
1368
1462
  ojParser p;
1369
- const byte *ptr = (const byte *)StringValuePtr(json);
1463
+ int frozen = OBJ_FROZEN(json);
1464
+ const byte *ptr;
1465
+
1466
+ if (!frozen) {
1467
+ rb_str_freeze(json);
1468
+ }
1469
+ ptr = (const byte *)StringValuePtr(json);
1370
1470
 
1371
1471
  TypedData_Get_Struct(self, struct _ojParser, &oj_parser_type, p);
1372
1472
 
1373
1473
  parser_reset(p);
1374
1474
  p->start(p);
1375
- parse(p, ptr);
1475
+ parse(p, ptr, (size_t)RSTRING_LEN(json), false);
1476
+ validate_document_end(p);
1376
1477
 
1377
1478
  return p->result(p);
1378
1479
  }
@@ -1392,7 +1493,13 @@ static VALUE load(VALUE self) {
1392
1493
  while (true) {
1393
1494
  rb_funcall(p->reader, oj_readpartial_id, 2, INT2NUM(16385), rbuf);
1394
1495
  if (0 < RSTRING_LEN(rbuf)) {
1395
- parse(p, (byte *)StringValuePtr(rbuf));
1496
+ parse(p, (byte *)StringValuePtr(rbuf), (size_t)RSTRING_LEN(rbuf), true);
1497
+ }
1498
+ if (Qtrue == rb_funcall(p->reader, oj_eofq_id, 0)) {
1499
+ if (0 < p->depth) {
1500
+ parse_error(p, "parse error, not closed");
1501
+ }
1502
+ break;
1396
1503
  }
1397
1504
  }
1398
1505
  return Qtrue;
@@ -1456,7 +1563,7 @@ static VALUE parser_file(VALUE self, VALUE filename) {
1456
1563
  while (true) {
1457
1564
  if (0 < (rsize = read(fd, buf, size))) {
1458
1565
  buf[rsize] = '\0';
1459
- parse(p, buf);
1566
+ parse(p, buf, rsize, true);
1460
1567
  }
1461
1568
  if (rsize <= 0) {
1462
1569
  if (0 != rsize) {
@@ -1567,6 +1674,28 @@ static VALUE parser_validate(VALUE self) {
1567
1674
  return validate_parser;
1568
1675
  }
1569
1676
 
1677
+ static VALUE parser_safe(int argc, VALUE *argv, VALUE self) {
1678
+ VALUE options;
1679
+
1680
+ if (1 == argc) {
1681
+ options = argv[0];
1682
+
1683
+ Check_Type(options, T_HASH);
1684
+ } else {
1685
+ options = rb_hash_new();
1686
+ }
1687
+
1688
+ ojParser p = OJ_R_ALLOC(struct _ojParser);
1689
+
1690
+ memset(p, 0, sizeof(struct _ojParser));
1691
+ buf_init(&p->key);
1692
+ buf_init(&p->buf);
1693
+ p->map = value_map;
1694
+ oj_set_parser_safe(p, options);
1695
+
1696
+ return TypedData_Wrap_Struct(parser_class, &oj_parser_type, p);
1697
+ }
1698
+
1570
1699
  /* Document-class: Oj::Parser
1571
1700
  *
1572
1701
  * A reusable parser that makes use of named delegates to determine the
@@ -1594,4 +1723,7 @@ void oj_parser_init(void) {
1594
1723
  rb_define_module_function(parser_class, "usual", parser_usual, 0);
1595
1724
  rb_define_module_function(parser_class, "saj", parser_saj, 0);
1596
1725
  rb_define_module_function(parser_class, "validate", parser_validate, 0);
1726
+ rb_define_module_function(parser_class, "safe", parser_safe, -1);
1727
+
1728
+ oj_safe_init(parser_class);
1597
1729
  }
data/ext/oj/parser.h CHANGED
@@ -32,9 +32,9 @@ typedef struct _num {
32
32
  long double dub;
33
33
  int64_t fixnum; // holds all digits
34
34
  uint32_t len;
35
- int16_t div; // 10^div
35
+ int16_t div; // 10^div
36
36
  int16_t exp;
37
- uint8_t shift; // shift of fixnum to get decimal
37
+ uint8_t shift; // shift of fixnum to get decimal
38
38
  bool neg;
39
39
  bool exp_neg;
40
40
  // for numbers as strings, reuse buf
@@ -42,6 +42,8 @@ typedef struct _num {
42
42
 
43
43
  struct _ojParser;
44
44
 
45
+ extern const rb_data_type_t oj_parser_type;
46
+
45
47
  typedef struct _funcs {
46
48
  void (*add_null)(struct _ojParser *p);
47
49
  void (*add_true)(struct _ojParser *p);