oj 3.17.4 → 3.17.5

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.
data/ext/oj/rails.c CHANGED
@@ -16,6 +16,10 @@ typedef struct _encoder {
16
16
  struct _rOptTable ropts;
17
17
  struct _options opts;
18
18
  VALUE arg;
19
+ // Each is false while the matching member is unused and the global it
20
+ // shadows - ropts or oj_default_options - is read instead.
21
+ bool own_ropts;
22
+ bool own_opts;
19
23
  } *Encoder;
20
24
 
21
25
  bool oj_rails_hash_opt = false;
@@ -83,6 +87,32 @@ static ROptTable copy_opts(ROptTable src, ROptTable dest) {
83
87
  return NULL;
84
88
  }
85
89
 
90
+ // The table an encoder reads from. Until the encoder is asked to optimize or
91
+ // deoptimize a class of its own it has no table and follows the global one.
92
+ // NULL is how oj_rails_get_opt() is told to use the global table, so the NULL
93
+ // has to reach it - handing it &e->ropts with an empty table instead would
94
+ // quietly turn every optimization off for this encoder.
95
+ static ROptTable encoder_ropts(Encoder e) {
96
+ return e->own_ropts ? &e->ropts : NULL;
97
+ }
98
+
99
+ // The options an encoder encodes with. See encoder_new() for why an encoder
100
+ // built without options follows the defaults instead of holding a copy.
101
+ static Options encoder_opts(Encoder e) {
102
+ return e->own_opts ? &e->opts : &oj_default_options;
103
+ }
104
+
105
+ // The table an encoder writes to. Copying the global table is deferred to here
106
+ // because it is a 6KB allocation and ActiveSupport builds an encoder for every
107
+ // encode call, while almost none of them ever optimize a class of their own.
108
+ static ROptTable encoder_own_ropts(Encoder e) {
109
+ if (!e->own_ropts) {
110
+ copy_opts(&ropts, &e->ropts);
111
+ e->own_ropts = true;
112
+ }
113
+ return &e->ropts;
114
+ }
115
+
86
116
  static int dump_attr_cb(ID key, VALUE value, VALUE ov) {
87
117
  Out out = (Out)ov;
88
118
  int depth = out->depth;
@@ -341,11 +371,16 @@ typedef struct _strLen {
341
371
  } *StrLen;
342
372
 
343
373
  static void dump_actioncontroller_parameters(VALUE obj, int depth, Out out, bool as_ok) {
374
+ int saved_argc = out->argc;
375
+ VALUE *saved_argv = out->argv;
376
+
344
377
  if (0 == parameters_id) {
345
378
  parameters_id = rb_intern("@parameters");
346
379
  }
347
380
  out->argc = 0;
348
381
  dump_rails_val(rb_ivar_get(obj, parameters_id), depth, out, true);
382
+ out->argc = saved_argc;
383
+ out->argv = saved_argv;
349
384
  }
350
385
 
351
386
  static StrLen columns_array(VALUE rcols, int *ccnt) {
@@ -375,7 +410,7 @@ static void dump_row(VALUE row, StrLen cols, int ccnt, int depth, Out out) {
375
410
 
376
411
  assure_size(out, 2);
377
412
  *out->cur++ = '{';
378
- size = depth * out->indent + 3;
413
+ size = indent_len(out, d2, out->opts->dump_opts.array_size) + 3;
379
414
  for (i = 0; i < ccnt; i++, cols++) {
380
415
  assure_size(out, size);
381
416
  if (out->opts->dump_opts.use) {
@@ -398,7 +433,7 @@ static void dump_row(VALUE row, StrLen cols, int ccnt, int depth, Out out) {
398
433
  *out->cur++ = ',';
399
434
  }
400
435
  }
401
- size = depth * out->indent + 1;
436
+ size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
402
437
  assure_size(out, size);
403
438
  if (out->opts->dump_opts.use) {
404
439
  if (0 < out->opts->dump_opts.array_size) {
@@ -423,7 +458,9 @@ static ID columns_id = 0;
423
458
  static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok) {
424
459
  volatile VALUE rows;
425
460
  StrLen cols;
426
- int ccnt = 0;
461
+ int ccnt = 0;
462
+ int saved_argc = out->argc;
463
+ VALUE *saved_argv = out->argv;
427
464
  size_t i;
428
465
  size_t rcnt;
429
466
  size_t size;
@@ -439,11 +476,7 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
439
476
  rcnt = RARRAY_LEN(rows);
440
477
  assure_size(out, 2);
441
478
  *out->cur++ = '[';
442
- if (out->opts->dump_opts.use) {
443
- size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
444
- } else {
445
- size = d2 * out->indent + 2;
446
- }
479
+ size = indent_len(out, d2, out->opts->dump_opts.array_size) + 1;
447
480
  assure_size(out, 2);
448
481
  for (i = 0; i < rcnt; i++) {
449
482
  assure_size(out, size);
@@ -466,7 +499,7 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
466
499
  }
467
500
  }
468
501
  OJ_R_FREE(cols);
469
- size = depth * out->indent + 1;
502
+ size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
470
503
  assure_size(out, size);
471
504
  if (out->opts->dump_opts.use) {
472
505
  if (0 < out->opts->dump_opts.array_size) {
@@ -483,6 +516,8 @@ static void dump_activerecord_result(VALUE obj, int depth, Out out, bool as_ok)
483
516
  fill_indent(out, depth);
484
517
  }
485
518
  *out->cur++ = ']';
519
+ out->argc = saved_argc;
520
+ out->argv = saved_argv;
486
521
  }
487
522
 
488
523
  typedef struct _namedFunc {
@@ -491,8 +526,16 @@ typedef struct _namedFunc {
491
526
  } *NamedFunc;
492
527
 
493
528
  static void dump_as_string(VALUE obj, int depth, Out out, bool as_ok) {
529
+ int saved_argc = out->argc;
530
+ VALUE *saved_argv = out->argv;
531
+
494
532
  if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
495
- out->argc = 0;
533
+ // oj_code_dump() writes the whole value and returns, so there is no
534
+ // subtree here to keep the options away from. Leave them for the next
535
+ // sibling. Restore instead of leaving them alone in case the encode
536
+ // consumed them.
537
+ out->argc = saved_argc;
538
+ out->argv = saved_argv;
496
539
  return;
497
540
  }
498
541
  oj_dump_obj_to_s(obj, out);
@@ -500,31 +543,53 @@ static void dump_as_string(VALUE obj, int depth, Out out, bool as_ok) {
500
543
 
501
544
  static void dump_as_json(VALUE obj, int depth, Out out, bool as_ok) {
502
545
  volatile VALUE ja;
546
+ bool selected;
547
+ int saved_argc = out->argc;
548
+ VALUE *saved_argv = out->argv;
503
549
 
504
550
  TRACE(out->opts->trace, "as_json", obj, depth + 1, TraceRubyIn);
505
551
  // Some classes elect to not take an options argument so check the arity
506
552
  // of as_json.
507
553
  if (0 == rb_obj_method_arity(obj, oj_as_json_id)) {
508
- ja = rb_funcall(obj, oj_as_json_id, 0);
554
+ ja = rb_funcall(obj, oj_as_json_id, 0);
555
+ selected = false; // as_json got no options so it did not do any :only/:except selection
509
556
  } else {
510
- ja = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
557
+ selected = (0 < out->argc); // Rails handed the :only/:except options to as_json
558
+ ja = rb_funcall2(obj, oj_as_json_id, out->argc, out->argv);
511
559
  }
512
560
  TRACE(out->opts->trace, "as_json", obj, depth + 1, TraceRubyOut);
513
561
 
514
562
  out->argc = 0;
515
- if (ja == obj || !as_ok) {
516
- // Once as_json is called it should never be called again on the same
517
- // object with as_ok.
518
- dump_rails_val(ja, depth, out, false);
519
- } else {
520
- int type = rb_type(ja);
563
+ // When as_json received the options it has already applied the Rails
564
+ // :only/:except selection at the attribute level (and kept the
565
+ // include_root_in_json wrapper and any :methods keys), so the Oj dump level
566
+ // :only/:except key filter must not be applied to the value it returned - a
567
+ // second pass would drop the wrapper (#1020) and the :methods keys (#1008).
568
+ // Suppress the filter only for this subtree (save and restore) so sibling
569
+ // keys outside the as_json result are still filtered. This is a flag on the
570
+ // dump state, not a change to out->opts: a StringWriter owns its options
571
+ // struct, so clearing only/except there would leak the buffers and kill the
572
+ // filter for every later push_value.
573
+ {
574
+ bool key_filter_off = out->key_filter_off;
521
575
 
522
- if (T_HASH == type || T_ARRAY == type) {
523
- dump_rails_val(ja, depth, out, true);
576
+ if (selected) {
577
+ out->key_filter_off = true;
578
+ }
579
+ if (ja == obj || !as_ok) {
580
+ // Once as_json is called it should never be called again on the same
581
+ // object with as_ok.
582
+ dump_rails_val(ja, depth, out, false);
524
583
  } else {
525
584
  dump_rails_val(ja, depth, out, true);
526
585
  }
586
+ out->key_filter_off = key_filter_off;
527
587
  }
588
+ // The options were consumed for this value only. Restore them so that the
589
+ // next sibling in a container is handed them too, the way ActiveSupport's
590
+ // Array#as_json and Hash#as_json pass them to every element.
591
+ out->argc = saved_argc;
592
+ out->argv = saved_argv;
528
593
  }
529
594
 
530
595
  static void dump_regexp(VALUE obj, int depth, Out out, bool as_ok) {
@@ -551,11 +616,16 @@ static VALUE activerecord_base = Qundef;
551
616
  static ID attributes_id = 0;
552
617
 
553
618
  static void dump_activerecord(VALUE obj, int depth, Out out, bool as_ok) {
619
+ int saved_argc = out->argc;
620
+ VALUE *saved_argv = out->argv;
621
+
554
622
  if (0 == attributes_id) {
555
623
  attributes_id = rb_intern("@attributes");
556
624
  }
557
625
  out->argc = 0;
558
626
  dump_rails_val(rb_ivar_get(obj, attributes_id), depth, out, true);
627
+ out->argc = saved_argc;
628
+ out->argv = saved_argv;
559
629
  }
560
630
 
561
631
  static ROpt create_opt(ROptTable rot, VALUE clas) {
@@ -591,6 +661,12 @@ static ROpt create_opt(ROptTable rot, VALUE clas) {
591
661
  ro->clas = clas;
592
662
  ro->on = true;
593
663
  ro->dump = dump_obj_attrs;
664
+ if (&ropts == rot) {
665
+ // Nothing marks the global table, so a class that lives only in it has
666
+ // to be a root. Encoders used to hold a copy of the table and mark it,
667
+ // which covered this by accident for as long as one was alive.
668
+ rb_gc_register_mark_object(clas);
669
+ }
594
670
  for (nf = dump_map; NULL != nf->name; nf++) {
595
671
  if (0 == strcmp(nf->name, classname)) {
596
672
  ro->dump = nf->func;
@@ -623,7 +699,9 @@ static void encoder_free(void *ptr) {
623
699
  if (NULL != ptr) {
624
700
  Encoder e = (Encoder)ptr;
625
701
 
626
- oj_options_release(&e->opts);
702
+ if (e->own_opts) {
703
+ oj_options_release(&e->opts);
704
+ }
627
705
  if (NULL != e->ropts.table) {
628
706
  OJ_R_FREE(e->ropts.table);
629
707
  }
@@ -636,15 +714,21 @@ static void encoder_mark(void *ptr) {
636
714
  Encoder e = (Encoder)ptr;
637
715
  int i;
638
716
 
639
- oj_options_mark(&e->opts);
717
+ if (e->own_opts) {
718
+ oj_options_mark(&e->opts);
719
+ }
640
720
  if (Qnil != e->arg) {
641
721
  rb_gc_mark(e->arg);
642
722
  }
643
723
  // The optimized classes in the encoder table are not reachable from
644
- // anywhere else once optimize() is called on the encoder itself.
645
- for (i = 0; i < e->ropts.len; i++) {
646
- if (Qnil != e->ropts.table[i].clas) {
647
- rb_gc_mark(e->ropts.table[i].clas);
724
+ // anywhere else once optimize() is called on the encoder itself. An
725
+ // encoder that never optimized anything has no table to walk - the
726
+ // classes in the global table are roots registered by create_opt().
727
+ if (NULL != e->ropts.table) {
728
+ for (i = 0; i < e->ropts.len; i++) {
729
+ if (Qnil != e->ropts.table[i].clas) {
730
+ rb_gc_mark(e->ropts.table[i].clas);
731
+ }
648
732
  }
649
733
  }
650
734
  }
@@ -670,21 +754,35 @@ static const rb_data_type_t oj_encoder_type = {
670
754
  static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
671
755
  Encoder e = OJ_R_ALLOC(struct _encoder);
672
756
 
673
- e->opts = oj_default_options;
674
- // Detach from the match_string regexps owned by the defaults before the
675
- // options are parsed so that a :match_string option builds a chain of its
676
- // own that is freed with the encoder.
677
- e->opts.str_rx.head = NULL;
678
- e->opts.str_rx.tail = NULL;
679
- copy_opts(&ropts, &e->ropts);
757
+ e->ropts.len = 0;
758
+ e->ropts.alen = 0;
759
+ e->ropts.table = NULL;
760
+ e->own_ropts = false;
680
761
 
681
762
  if (1 <= argc && Qnil != *argv) {
682
763
  e->arg = *argv;
683
764
  } else {
684
765
  e->arg = rb_hash_new();
685
766
  }
686
- oj_parse_options(e->arg, &e->opts);
687
- oj_options_take_ownership(&e->opts);
767
+ // An encoder given no options of its own reads oj_default_options at encode
768
+ // time rather than copying it here. ActiveSupport keeps one option-less
769
+ // encoder for the life of the process, so a copy taken now would freeze
770
+ // every later Oj.default_options and ActiveSupport::JSON::Encoding change
771
+ // out of it - including the time_precision that set_encoder itself writes
772
+ // after ActiveSupport has already built and cached that encoder.
773
+ e->own_opts = T_HASH == rb_type(e->arg) && 0 < RHASH_SIZE(e->arg);
774
+ if (e->own_opts) {
775
+ e->opts = oj_default_options;
776
+ // Detach from the match_string regexps owned by the defaults before the
777
+ // options are parsed so that a :match_string option builds a chain of
778
+ // its own that is freed with the encoder.
779
+ e->opts.str_rx.head = NULL;
780
+ e->opts.str_rx.tail = NULL;
781
+ oj_parse_options(e->arg, &e->opts);
782
+ oj_options_take_ownership(&e->opts);
783
+ } else {
784
+ memset(&e->opts, 0, sizeof(e->opts));
785
+ }
688
786
 
689
787
  return TypedData_Wrap_Struct(encoder_class, &oj_encoder_type, e);
690
788
  }
@@ -781,7 +879,7 @@ static VALUE encoder_optimize(int argc, VALUE *argv, VALUE self) {
781
879
  Encoder e;
782
880
  TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
783
881
 
784
- optimize(argc, argv, &e->ropts, true);
882
+ optimize(argc, argv, encoder_own_ropts(e), true);
785
883
 
786
884
  return Qnil;
787
885
  }
@@ -838,7 +936,7 @@ static VALUE encoder_deoptimize(int argc, VALUE *argv, VALUE self) {
838
936
  Encoder e;
839
937
  TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
840
938
 
841
- optimize(argc, argv, &e->ropts, false);
939
+ optimize(argc, argv, encoder_own_ropts(e), false);
842
940
 
843
941
  return Qnil;
844
942
  }
@@ -869,7 +967,7 @@ static VALUE encoder_optimized(VALUE self, VALUE clas) {
869
967
  ROpt ro;
870
968
 
871
969
  TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
872
- ro = oj_rails_get_opt(&e->ropts, clas);
970
+ ro = oj_rails_get_opt(encoder_ropts(e), clas);
873
971
 
874
972
  if (NULL == ro) {
875
973
  return Qfalse;
@@ -978,11 +1076,22 @@ static VALUE encoder_encode(VALUE self, VALUE obj) {
978
1076
  TypedData_Get_Struct(self, struct _encoder, &oj_encoder_type, e);
979
1077
 
980
1078
  if (Qnil != e->arg) {
981
- VALUE argv[1] = {e->arg};
982
-
983
- return encode(obj, &e->ropts, &e->opts, 1, argv);
984
- }
985
- return encode(obj, &e->ropts, &e->opts, 0, NULL);
1079
+ // as_json is allowed to write into the options hash it is handed, and
1080
+ // the ActiveSupport suite has a test that does exactly that. Handing
1081
+ // out the encoder's own hash lets one such call poison every later
1082
+ // encode by the same encoder, which on ActiveSupport 8.1 is every
1083
+ // option-less to_json in the process because json_encoder= caches one
1084
+ // encoder and keeps it. Rails never exposes a hash it will reuse:
1085
+ // JSONGemEncoder#encode passes options.dup, and only when there are
1086
+ // options at all, so with none as_json writes into its own default.
1087
+ // The copy is not frozen the way Rails 8.0 and later freeze theirs,
1088
+ // because Oj hands as_json the hash even when it is empty and that is
1089
+ // exactly the case Rails leaves writable.
1090
+ VALUE argv[1] = {T_HASH == rb_type(e->arg) ? rb_hash_dup(e->arg) : e->arg};
1091
+
1092
+ return encode(obj, encoder_ropts(e), encoder_opts(e), 1, argv);
1093
+ }
1094
+ return encode(obj, encoder_ropts(e), encoder_opts(e), 0, NULL);
986
1095
  }
987
1096
 
988
1097
  /* Document-method: encode
@@ -1246,11 +1355,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
1246
1355
  if (0 == cnt) {
1247
1356
  *out->cur++ = ']';
1248
1357
  } else {
1249
- if (out->opts->dump_opts.use) {
1250
- size = d2 * out->opts->dump_opts.indent_size + out->opts->dump_opts.array_size + 1;
1251
- } else {
1252
- size = d2 * out->indent + 2;
1253
- }
1358
+ size = indent_len(out, d2, out->opts->dump_opts.array_size) + 1;
1254
1359
  assure_size(out, size * cnt);
1255
1360
  cnt--;
1256
1361
  for (i = 0; i <= cnt; i++) {
@@ -1272,7 +1377,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
1272
1377
  *out->cur++ = ',';
1273
1378
  }
1274
1379
  }
1275
- size = depth * out->indent + 1;
1380
+ size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
1276
1381
  assure_size(out, size);
1277
1382
  if (out->opts->dump_opts.use) {
1278
1383
  if (0 < out->opts->dump_opts.array_size) {
@@ -1302,7 +1407,7 @@ static int hash_cb(VALUE key, VALUE value, VALUE ov) {
1302
1407
  if (out->omit_nil && Qnil == value) {
1303
1408
  return ST_CONTINUE;
1304
1409
  }
1305
- if (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except) {
1410
+ if (!out->key_filter_off && (NULL != out->opts->dump_opts.only || NULL != out->opts->dump_opts.except)) {
1306
1411
  if (oj_key_skip(key, out->opts->dump_opts.only, out->opts->dump_opts.except)) {
1307
1412
  return ST_CONTINUE;
1308
1413
  }
@@ -1400,10 +1505,15 @@ static void dump_hash(VALUE obj, int depth, Out out, bool as_ok) {
1400
1505
  }
1401
1506
 
1402
1507
  static void dump_obj(VALUE obj, int depth, Out out, bool as_ok) {
1403
- VALUE clas;
1508
+ VALUE clas;
1509
+ int saved_argc = out->argc;
1510
+ VALUE *saved_argv = out->argv;
1404
1511
 
1405
1512
  if (oj_code_dump(oj_compat_codes, obj, depth, out)) {
1406
- out->argc = 0;
1513
+ // Same as in dump_as_string(): the value is complete, so the options
1514
+ // stay available for the next sibling.
1515
+ out->argc = saved_argc;
1516
+ out->argv = saved_argv;
1407
1517
  return;
1408
1518
  }
1409
1519
  clas = rb_obj_class(obj);
data/ext/oj/reader.c CHANGED
@@ -88,8 +88,8 @@ void oj_reader_init(Reader reader, VALUE io, int fd, bool to_s) {
88
88
  }
89
89
 
90
90
  int oj_reader_read(Reader reader) {
91
- int err;
92
- size_t shift = 0;
91
+ int err;
92
+ ptrdiff_t shift = 0;
93
93
 
94
94
  if (0 == reader->read_func) {
95
95
  return -1;
data/ext/oj/resolve.c CHANGED
@@ -19,6 +19,9 @@ inline static VALUE resolve_classname(VALUE mod, const char *classname, int auto
19
19
 
20
20
  if (rb_const_defined_at(mod, ci)) {
21
21
  clas = rb_const_get_at(mod, ci);
22
+ if (!RB_TYPE_P(clas, T_CLASS) && !RB_TYPE_P(clas, T_MODULE)) {
23
+ clas = Qundef;
24
+ }
22
25
  } else if (auto_define) {
23
26
  clas = rb_define_class_under(mod, classname, oj_bag_class);
24
27
  } else {
@@ -32,7 +35,8 @@ static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int a
32
35
  VALUE clas;
33
36
  char *end = class_name + sizeof(class_name) - 1;
34
37
  char *s;
35
- const char *n = name;
38
+ const char *n = name;
39
+ size_t nlen = len;
36
40
 
37
41
  clas = rb_cObject;
38
42
  for (s = class_name; 0 < len; n++, len--) {
@@ -55,7 +59,12 @@ static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int a
55
59
  }
56
60
  *s = '\0';
57
61
  if (Qundef == (clas = resolve_classname(clas, class_name, auto_define))) {
58
- oj_set_error_at(pi, error_class, __FILE__, __LINE__, "class %s is not defined", name);
62
+ if (sizeof(class_name) <= nlen) {
63
+ nlen = sizeof(class_name) - 1;
64
+ }
65
+ memcpy(class_name, name, nlen);
66
+ class_name[nlen] = '\0';
67
+ oj_set_error_at(pi, error_class, __FILE__, __LINE__, "class '%s' is not defined", class_name);
59
68
  if (Qnil != error_class) {
60
69
  pi->err_class = error_class;
61
70
  }
data/ext/oj/safe.c CHANGED
@@ -5,7 +5,7 @@ static VALUE max_hash_size_sym, max_array_size_sym, max_depth_sym, max_total_ele
5
5
  max_array_size_error_class, max_depth_error_class, max_total_elements_error_class;
6
6
 
7
7
  static void check_object_size(safe_T safe) {
8
- if (NIL_P(safe->max_hash_size)) {
8
+ if (!safe->max_hash_size_isset) {
9
9
  return;
10
10
  }
11
11
 
@@ -23,7 +23,7 @@ static void check_object_size(safe_T safe) {
23
23
  }
24
24
 
25
25
  static void check_array_size(safe_T safe) {
26
- if (NIL_P(safe->max_array_size)) {
26
+ if (!safe->max_array_size_isset) {
27
27
  return;
28
28
  }
29
29
 
@@ -41,7 +41,7 @@ static void check_array_size(safe_T safe) {
41
41
  }
42
42
 
43
43
  static void check_max_depth(safe_T safe, ojParser p) {
44
- if (NIL_P(safe->max_depth) || safe->max_depth >= (p->depth + 1)) {
44
+ if (!safe->max_depth_isset || safe->max_depth >= (p->depth + 1)) {
45
45
  return;
46
46
  }
47
47
 
@@ -55,7 +55,7 @@ static void check_max_total_elements(safe_T safe) {
55
55
  * null, true) are not counted. As a result, `current_elements_count`
56
56
  * always holds one less than the actual total.
57
57
  */
58
- if (NIL_P(safe->max_total_elements) || safe->max_total_elements > safe->current_elements_count) {
58
+ if (!safe->max_total_elements_isset || safe->max_total_elements > safe->current_elements_count) {
59
59
  return;
60
60
  }
61
61
 
@@ -140,58 +140,72 @@ DEFINE_DELEGATED_OBJECT_FUNCTION(add_float);
140
140
  DEFINE_DELEGATED_OBJECT_FUNCTION(add_big);
141
141
  DEFINE_DELEGATED_OBJECT_FUNCTION(add_str);
142
142
 
143
+ // The counting wrappers live in the parser function table, and the option
144
+ // setters of the usual parser write to the same slots, so an option set after
145
+ // the parser was built puts the unwrapped handler back and the limits stop
146
+ // being applied for that kind of value.
147
+ #define WRAP(funcs, slot, wrapper, saved) \
148
+ do { \
149
+ if (wrapper != (funcs)->slot) { \
150
+ safe->saved = (funcs)->slot; \
151
+ } \
152
+ (funcs)->slot = wrapper; \
153
+ } while (0)
154
+
155
+ static void wrap_funcs(ojParser p, safe_T safe) {
156
+ Funcs f = &p->funcs[ARRAY_FUN];
157
+
158
+ WRAP(f, open_object, safe_open_object, delegated_open_object_func);
159
+ WRAP(f, open_array, safe_open_array, delegated_open_array_func);
160
+ // The following overrides are done for counting objects
161
+ WRAP(f, add_null, safe_add_null, delegated_add_null_func);
162
+ WRAP(f, add_true, safe_add_true, delegated_add_true_func);
163
+ WRAP(f, add_false, safe_add_false, delegated_add_false_func);
164
+ WRAP(f, add_int, safe_add_int, delegated_add_int_func);
165
+ WRAP(f, add_float, safe_add_float, delegated_add_float_func);
166
+ WRAP(f, add_big, safe_add_big, delegated_add_big_func);
167
+ WRAP(f, add_str, safe_add_str, delegated_add_str_func);
168
+
169
+ f = &p->funcs[OBJECT_FUN];
170
+ WRAP(f, open_object, safe_open_object_key, delegated_open_object_key_func);
171
+ WRAP(f, open_array, safe_open_array_key, delegated_open_array_key_func);
172
+ WRAP(f, add_null, safe_add_null_key, delegated_add_null_key_func);
173
+ WRAP(f, add_true, safe_add_true_key, delegated_add_true_key_func);
174
+ WRAP(f, add_false, safe_add_false_key, delegated_add_false_key_func);
175
+ WRAP(f, add_int, safe_add_int_key, delegated_add_int_key_func);
176
+ WRAP(f, add_float, safe_add_float_key, delegated_add_float_key_func);
177
+ WRAP(f, add_big, safe_add_big_key, delegated_add_big_key_func);
178
+ WRAP(f, add_str, safe_add_str_key, delegated_add_str_key_func);
179
+ }
180
+
181
+ static VALUE safe_option(ojParser p, const char *key, VALUE value) {
182
+ safe_T safe = (safe_T)p->ctx;
183
+ VALUE rv;
184
+
185
+ if (0 == strcmp("omit_null", key)) {
186
+ return safe->omit_null ? Qtrue : Qfalse;
187
+ }
188
+ rv = safe->delegated_option_func(p, key, value);
189
+ if (0 == strcmp("omit_null=", key)) {
190
+ safe->omit_null = (Qtrue == rv);
191
+ }
192
+ wrap_funcs(p, safe);
193
+
194
+ return rv;
195
+ }
196
+
143
197
  void oj_init_safe_parser(ojParser p, safe_T safe, VALUE options) {
144
198
  // Safe parser inherits all members of usual parser
145
199
  oj_init_usual(p, &safe->usual);
146
200
 
147
201
  safe->delegated_start_func = p->start;
148
202
  p->start = safe_start;
203
+ safe->omit_null = false;
149
204
 
150
- Funcs f;
205
+ wrap_funcs(p, safe);
151
206
 
152
- // Array parser functions
153
- f = &p->funcs[ARRAY_FUN];
154
- safe->delegated_open_object_func = f->open_object;
155
- f->open_object = safe_open_object;
156
- safe->delegated_open_array_func = f->open_array;
157
- f->open_array = safe_open_array;
158
- // The following overrides are done for counting objects
159
- safe->delegated_add_null_func = f->add_null;
160
- f->add_null = safe_add_null;
161
- safe->delegated_add_true_func = f->add_true;
162
- f->add_true = safe_add_true;
163
- safe->delegated_add_false_func = f->add_false;
164
- f->add_false = safe_add_false;
165
- safe->delegated_add_int_func = f->add_int;
166
- f->add_int = safe_add_int;
167
- safe->delegated_add_float_func = f->add_float;
168
- f->add_float = safe_add_float;
169
- safe->delegated_add_big_func = f->add_big;
170
- f->add_big = safe_add_big;
171
- safe->delegated_add_str_func = f->add_str;
172
- f->add_str = safe_add_str;
173
-
174
- // Object parser functions
175
- f = &p->funcs[OBJECT_FUN];
176
- safe->delegated_open_object_key_func = f->open_object;
177
- f->open_object = safe_open_object_key;
178
- safe->delegated_open_array_key_func = f->open_array;
179
- f->open_array = safe_open_array_key;
180
- // The following overrides are done for counting objects
181
- safe->delegated_add_null_key_func = f->add_null;
182
- f->add_null = safe_add_null_key;
183
- safe->delegated_add_true_key_func = f->add_true;
184
- f->add_true = safe_add_true_key;
185
- safe->delegated_add_false_key_func = f->add_false;
186
- f->add_false = safe_add_false_key;
187
- safe->delegated_add_int_key_func = f->add_int;
188
- f->add_int = safe_add_int_key;
189
- safe->delegated_add_float_key_func = f->add_float;
190
- f->add_float = safe_add_float_key;
191
- safe->delegated_add_big_key_func = f->add_big;
192
- f->add_big = safe_add_big_key;
193
- safe->delegated_add_str_key_func = f->add_str;
194
- f->add_str = safe_add_str_key;
207
+ safe->delegated_option_func = p->option;
208
+ p->option = safe_option;
195
209
 
196
210
  SET_CONFIG(max_hash_size);
197
211
  SET_CONFIG(max_array_size);
data/ext/oj/safe.h CHANGED
@@ -8,11 +8,13 @@
8
8
  VALUE rb_##config_name = rb_hash_aref(options, config_name##_sym); \
9
9
  \
10
10
  if (RB_INTEGER_TYPE_P(rb_##config_name)) { \
11
- safe->config_name = NUM2LONG(rb_##config_name); \
11
+ safe->config_name = NUM2LONG(rb_##config_name); \
12
+ safe->config_name##_isset = true; \
12
13
  } else if (!NIL_P(rb_##config_name)) { \
13
14
  rb_raise(rb_eArgError, "Incorrect value provided for `" #config_name "`"); \
14
15
  } else { \
15
- safe->config_name = Qnil; \
16
+ safe->config_name = 0; \
17
+ safe->config_name##_isset = false; \
16
18
  } \
17
19
  } while (0);
18
20
 
@@ -49,10 +51,20 @@ typedef struct _safe_S {
49
51
  long int max_total_elements;
50
52
  long int max_json_size_bytes;
51
53
 
54
+ bool max_hash_size_isset;
55
+ bool max_array_size_isset;
56
+ bool max_depth_isset;
57
+ bool max_total_elements_isset;
58
+
52
59
  long int current_hash_size;
53
60
  long int current_array_size;
54
61
  long int current_elements_count;
55
62
 
63
+ // omit_null is kept here because the usual parser reports it by looking at
64
+ // the add_null slot, which is one of the slots wrapped below.
65
+ bool omit_null;
66
+
67
+ VALUE (*delegated_option_func)(struct _ojParser *p, const char *key, VALUE value);
56
68
  void (*delegated_start_func)(struct _ojParser *p);
57
69
 
58
70
  // Array functions
data/ext/oj/saj.c CHANGED
@@ -198,6 +198,12 @@ static void read_hash(ParseInfo pi, const char *key) {
198
198
  } else {
199
199
  while (1) {
200
200
  next_non_white(pi);
201
+ if ('"' != *pi->s) {
202
+ if (pi->has_error) {
203
+ call_error("invalid format, expected a key", pi, __FILE__, __LINE__);
204
+ }
205
+ raise_error("invalid format, expected a key", pi->str, pi->s);
206
+ }
201
207
  ks = read_quoted_value(pi);
202
208
  next_non_white(pi);
203
209
  if (':' == *pi->s) {