oj 3.17.7 → 3.17.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d8e7bd60ca653140f1d101a88ded37ad5b6fe2af3d5e01ae6787435238c75eb
4
- data.tar.gz: 9ef057d4e77c5b2ba30f9a34b2fc3621875c902ca05da25495a4abf657f0bbdb
3
+ metadata.gz: 887bd6056f15f0ed95de347bc87d12c00c2de9a071b972f16be14952c408f6a6
4
+ data.tar.gz: af830ec844053ce4c955ac193260d53f5edf88159038c30e5c047b8fd9b334d7
5
5
  SHA512:
6
- metadata.gz: b9654f35d338c316388534ee7d7143165dd2aab82960324f7fd313771445a2dc2d2b22964408b533735dab30e4fd554b5f8abe272a7b3beb5352720cd039fb16
7
- data.tar.gz: 4ffc5a78fc866f6824564d34e2db316c56454999bdcb0f91068b40e4dd28f04f23acfb9ce819364bb6a33c3aa5c2e3be0979982220552a1ec3a5eceedfd6f320
6
+ metadata.gz: 1aa3a234183cfd1e86b6a2b08f91ec553a6048405042ca887647e43198a08ab06287329389bb0c0d4f6c73a637eff158e29da7f187ae4778ebab7379984c771d
7
+ data.tar.gz: 6e1604f05a6b48364549b350c9b18c4e9b1c5dd819881d82e172ee466ce9c10ff49fbb60309f56ec951ce99f0c2791ca31b94a12e23ebef2bde40c0d44a0a8c4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.17.8 - 2026-11-03
4
+
5
+ ### Fixed
6
+
7
+ - Fixed issue #988 by no longer requiring `ostruct` when Oj is loaded, autoloading `JSON::GenericObject` instead. (#1113)
8
+ - Always include `cpuid.h` and fix the cache typedef for older compilers. (#1112)
9
+ - Keep the sign of `-0.0` when dumping in compat and rails modes. (#1115)
10
+ - Fixed big negative integers and big decimals in `Oj::Parser.usual`. (#1116)
11
+ - Reject a value after a number and whitespace in `Oj::Parser`. (#1117)
12
+ - Combine an escaped surrogate pair in `Oj::Parser`. (#1118)
13
+ - Reject an object close after a colon in `Oj::Parser`. (#1119)
14
+ - Accept an exponent right after a zero integer part in `Oj::Parser`. (#1121)
15
+ - Raise for an incomplete top-level number in `Oj::Parser`. (#1122)
16
+ - Reject unescaped control characters in strict and compat mode strings. (#1123)
17
+ - Fixed memory and handle leaks in `Oj.load` with recent winpthreads on Windows. (#1125)
18
+ - Fixed writer state after an excess pop. (#1126)
19
+ - Fixed memory corruption when `^u` names a class that is not a Struct. (#1127)
20
+ - Fixed cached keys and strings containing NUL returning a different value. (#1129)
21
+ - Fixed a segmentation fault on a `create_id` class path through a non-module constant. (#1130)
22
+ - Fixed a segmentation fault with the `Oj::Parser` class cache after GC compaction. (#1131)
23
+ - Fixed a use-after-free of converted non-UTF-8 input in `Oj.load`. (#1132)
24
+ - Fixed a use-after-free of converted non-UTF-8 strings in `Oj.dump`. (#1133)
25
+ - Fixed a segmentation fault when `Oj::Parser#parse` is given a non-String. (#1134)
26
+ - Fixed a segmentation fault when `Oj.safe_load` is given a null StringIO. (#1135)
27
+ - Fixed a segmentation fault and memory leak in `Oj.saj_parse` with IO input. (#1136)
28
+ - Fixed a segmentation fault when `to_json` returns a non-String in custom mode. (#1137)
29
+ - Fixed a segmentation fault when an exception message is not a String in compat mode. (#1138)
30
+ - Fixed a segmentation fault when `raw_json` returns a non-String. (#1139)
31
+ - Fixed a use-after-free of keys in the `Oj::Parser` saj delegate. (#1140)
32
+ - Fixed wrong keys and `IndexError` for `Oj::Parser` saj handlers with only some callbacks. (#1141)
33
+ - Fixed a buffer overflow when dumping nested arrays with a String indent. (#1142)
34
+ - Fixed a segmentation fault when `array_class` is not an Array in compat and custom mode. (#1143)
35
+ - Fixed a double free when closing an `Oj::Doc` inside the open block. (#1144)
36
+ - Fixed buffer leaks and unchecked file sizes when reading special files. (#1145)
37
+ - Fixed reading freed memory when `Oj.load` parses an object through `to_s`. (#1146)
38
+ - Fixed reading a freed buffer when a `StringWriter` is pushed into itself. (#1147)
39
+ - Fixed a segmentation fault and use-after-free when using a closed `Oj::Doc`. (#1148)
40
+ - Fixed reading and writing a different file when a path contains a NUL byte. (#1149)
41
+ - Fixed buffer leaks when a dump to a String, file, or stream raises. (#1152)
42
+ - Fixed unknown class names filling the class cache and loading as a Hash. (#1153)
43
+ - Fixed object mode ranges and exceptions on Ruby 4.1. (#1154)
44
+ - Fixed a segmentation fault after `Oj::Doc#move` fails on a path with `..`. (#1155)
45
+ - Fixed a segmentation fault in wab mode after GC compaction. (#1156)
46
+ - Fixed OpenStruct dumps and loads changing after GC compaction. (#1157)
47
+ - Fixed a segmentation fault when pushing to a writer with `circular: true`. (#1158)
48
+ - Fixed exception loading and Range subclass dumps in custom mode on Ruby 4.1. (#1159)
49
+
50
+ ### Performance
51
+
52
+ - Speed up parsing of object keys that collide in the key cache by using SipHash. (#1150)
53
+ - Speed up dumping ASCII-only strings that are not UTF-8. (#1160)
54
+ - Speed up dumping objects in object and custom modes. (#1161)
55
+ - Speed up dumping plain objects in custom mode. (#1162)
56
+
3
57
  ## 3.17.7 - 2026-09-29
4
58
 
5
59
  ### Fixed
data/ext/oj/cache.c CHANGED
@@ -25,23 +25,41 @@
25
25
  #define CACHE_UNLOCK(c) rb_mutex_unlock((c)->mutex)
26
26
  #endif
27
27
 
28
- // almost the Murmur hash algorithm
29
- #define M 0x5bd1e995
30
-
31
- uint64_t oj_hash_seed = 0;
28
+ #define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
29
+ #define SIPROUND \
30
+ do { \
31
+ v0 += v1; \
32
+ v1 = ROTL(v1, 13); \
33
+ v1 ^= v0; \
34
+ v0 = ROTL(v0, 32); \
35
+ v2 += v3; \
36
+ v3 = ROTL(v3, 16); \
37
+ v3 ^= v2; \
38
+ v0 += v3; \
39
+ v3 = ROTL(v3, 21); \
40
+ v3 ^= v0; \
41
+ v2 += v1; \
42
+ v1 = ROTL(v1, 17); \
43
+ v1 ^= v2; \
44
+ v2 = ROTL(v2, 32); \
45
+ } while (0)
46
+
47
+ static uint64_t hash_key[2];
32
48
 
33
49
  void oj_hash_seed_init(void) {
34
50
  static bool seeded = false;
51
+ VALUE seed;
52
+ VALUE mask;
35
53
 
36
54
  if (seeded) {
37
55
  return;
38
56
  }
39
- seeded = true;
40
- #if HAVE_RB_HASH_START
41
- oj_hash_seed = (uint64_t)rb_hash_start(0);
42
- #else
43
- oj_hash_seed = (uint64_t)NUM2LL(rb_funcall(rb_str_new_cstr("oj"), rb_intern("hash"), 0));
44
- #endif
57
+ seed = rb_funcall(rb_cRandom, rb_intern("new_seed"), 0);
58
+ mask = ULL2NUM(UINT64_MAX);
59
+ hash_key[0] = NUM2ULL(rb_funcall(seed, rb_intern("&"), 1, mask)) ^ (uint64_t)rb_memhash("oj0", 3);
60
+ hash_key[1] = NUM2ULL(rb_funcall(rb_funcall(seed, rb_intern(">>"), 1, INT2FIX(64)), rb_intern("&"), 1, mask)) ^
61
+ (uint64_t)rb_memhash("oj1", 3);
62
+ seeded = true;
45
63
  }
46
64
 
47
65
  typedef struct _slot {
@@ -53,7 +71,7 @@ typedef struct _slot {
53
71
  char key[CACHE_MAX_KEY];
54
72
  } *Slot;
55
73
 
56
- typedef struct _cache {
74
+ struct _cache {
57
75
  volatile Slot *slots;
58
76
  volatile size_t cnt;
59
77
  VALUE (*form)(const char *str, size_t len);
@@ -69,44 +87,43 @@ typedef struct _cache {
69
87
  #endif
70
88
  uint8_t xrate;
71
89
  bool mark;
72
- } *Cache;
90
+ };
73
91
 
74
92
  void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) {
75
93
  c->form = form;
76
94
  }
77
95
 
78
- static uint64_t hash_calc(const uint8_t *key, size_t len) {
79
- const uint8_t *end = key + len;
80
- const uint8_t *endless = key + (len & 0xFFFFFFFC);
81
- uint64_t h = (uint64_t)len ^ oj_hash_seed;
82
- uint64_t k;
83
-
84
- while (key < endless) {
85
- k = (uint64_t)*key++;
86
- k |= (uint64_t)*key++ << 8;
87
- k |= (uint64_t)*key++ << 16;
88
- k |= (uint64_t)*key++ << 24;
89
-
90
- k *= M;
91
- k ^= k >> 24;
92
- h *= M;
93
- h ^= k * M;
94
- }
95
- if (1 < end - key) {
96
- uint16_t k16 = (uint16_t)*key++;
97
-
98
- k16 |= (uint16_t)*key++ << 8;
99
- h ^= k16 << 8;
96
+ uint64_t oj_hash_calc(const uint8_t *key, size_t len) {
97
+ const uint8_t *end = key + (len & ~(size_t)7);
98
+ uint64_t v0 = 0x736f6d6570736575ULL ^ hash_key[0];
99
+ uint64_t v1 = 0x646f72616e646f6dULL ^ hash_key[1];
100
+ uint64_t v2 = 0x6c7967656e657261ULL ^ hash_key[0];
101
+ uint64_t v3 = 0x7465646279746573ULL ^ hash_key[1];
102
+ uint64_t b = (uint64_t)len << 56;
103
+ uint64_t m;
104
+ size_t i;
105
+
106
+ for (; key < end; key += 8) {
107
+ m = 0;
108
+ for (i = 0; i < 8; i++) {
109
+ m |= (uint64_t)key[i] << (8 * i);
110
+ }
111
+ v3 ^= m;
112
+ SIPROUND;
113
+ v0 ^= m;
100
114
  }
101
- if (key < end) {
102
- h ^= *key;
115
+ for (i = 0; i < (len & 7); i++) {
116
+ b |= (uint64_t)key[i] << (8 * i);
103
117
  }
104
- h *= M;
105
- h ^= h >> 13;
106
- h *= M;
107
- h ^= h >> 15;
108
-
109
- return h;
118
+ v3 ^= b;
119
+ SIPROUND;
120
+ v0 ^= b;
121
+ v2 ^= 0xff;
122
+ SIPROUND;
123
+ SIPROUND;
124
+ SIPROUND;
125
+
126
+ return v0 ^ v1 ^ v2 ^ v3;
110
127
  }
111
128
 
112
129
  static void rehash(Cache c) {
@@ -137,7 +154,7 @@ static void rehash(Cache c) {
137
154
  }
138
155
 
139
156
  static VALUE lockless_intern(Cache c, const char *key, size_t len) {
140
- uint64_t h = hash_calc((const uint8_t *)key, len);
157
+ uint64_t h = oj_hash_calc((const uint8_t *)key, len);
141
158
  Slot *bucket = (Slot *)c->slots + (h & c->mask);
142
159
  Slot b;
143
160
  volatile VALUE rkey;
@@ -153,7 +170,7 @@ static VALUE lockless_intern(Cache c, const char *key, size_t len) {
153
170
  }
154
171
  }
155
172
  for (b = *bucket; NULL != b; b = b->next) {
156
- if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
173
+ if ((uint8_t)len == b->klen && 0 == memcmp(b->key, key, len)) {
157
174
  b->use_cnt += 16;
158
175
  return b->val;
159
176
  }
@@ -198,10 +215,10 @@ static VALUE locking_intern(Cache c, const char *key, size_t len) {
198
215
  c->rcnt = 0;
199
216
  }
200
217
  }
201
- h = hash_calc((const uint8_t *)key, len);
218
+ h = oj_hash_calc((const uint8_t *)key, len);
202
219
  bucket = (Slot *)c->slots + (h & c->mask);
203
220
  for (b = *bucket; NULL != b; b = b->next) {
204
- if ((uint8_t)len == b->klen && 0 == strncmp(b->key, key, len)) {
221
+ if ((uint8_t)len == b->klen && 0 == memcmp(b->key, key, len)) {
205
222
  b->use_cnt += 4;
206
223
  CACHE_UNLOCK(c);
207
224
 
@@ -230,7 +247,7 @@ static VALUE locking_intern(Cache c, const char *key, size_t len) {
230
247
  // Lock again to add the new entry.
231
248
  CACHE_LOCK(c);
232
249
  if (old_size != c->size) {
233
- h = hash_calc((const uint8_t *)key, len);
250
+ h = oj_hash_calc((const uint8_t *)key, len);
234
251
  bucket = (Slot *)c->slots + (h & c->mask);
235
252
  }
236
253
  b->next = *bucket;
data/ext/oj/cache.h CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  #define CACHE_MAX_KEY 35
12
12
 
13
- extern uint64_t oj_hash_seed;
13
+ extern uint64_t oj_hash_calc(const uint8_t *key, size_t len);
14
14
 
15
15
  extern void oj_hash_seed_init(void);
16
16
 
data/ext/oj/code.c CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  #include "code.h"
5
5
 
6
+ #include <string.h>
7
+
6
8
  #include "dump.h"
7
9
 
8
10
  inline static VALUE resolve_classname(VALUE mod, const char *classname) {
@@ -45,18 +47,44 @@ static VALUE path2class(const char *name) {
45
47
  return resolve_classname(clas, class_name);
46
48
  }
47
49
 
50
+ // Resolves the class lazily and returns Qundef while it is not defined, as
51
+ // OpenStruct is not until ostruct is required. A miss is checked again on
52
+ // every call so a class loaded later is found by whichever dump comes next,
53
+ // through any entry point. For a top-level name that check is a single
54
+ // constant table probe, so it costs about the same as a cached miss would.
55
+ VALUE oj_code_class(Code c) {
56
+ if (Qnil != c->clas && Qundef != c->clas) {
57
+ return c->clas;
58
+ }
59
+ if (0 == c->id && NULL == strstr(c->name, "::")) {
60
+ c->id = rb_intern(c->name);
61
+ }
62
+ if (0 == c->id) {
63
+ c->clas = path2class(c->name);
64
+ } else if (rb_const_defined_at(rb_cObject, c->id)) {
65
+ c->clas = rb_const_get_at(rb_cObject, c->id);
66
+ } else {
67
+ c->clas = Qundef;
68
+ }
69
+ if (Qnil != c->clas && Qundef != c->clas) {
70
+ rb_gc_register_address(&c->clas);
71
+ }
72
+ return c->clas;
73
+ }
74
+
75
+ inline static bool code_is(Code c, VALUE clas) {
76
+ if (Qnil != c->clas && Qundef != c->clas) {
77
+ return clas == c->clas;
78
+ }
79
+ return clas == oj_code_class(c);
80
+ }
81
+
48
82
  bool oj_code_dump(Code codes, VALUE obj, int depth, Out out) {
49
83
  VALUE clas = rb_obj_class(obj);
50
84
  Code c = codes;
51
85
 
52
86
  for (; NULL != c->name; c++) {
53
- if (Qundef == c->clas) { // indicates not defined
54
- continue;
55
- }
56
- if (Qnil == c->clas) {
57
- c->clas = path2class(c->name);
58
- }
59
- if (clas == c->clas && c->active) {
87
+ if (c->active && code_is(c, clas)) {
60
88
  c->encode(obj, depth, out);
61
89
  return true;
62
90
  }
@@ -69,13 +97,7 @@ oj_code_load(Code codes, VALUE clas, VALUE args) {
69
97
  Code c = codes;
70
98
 
71
99
  for (; NULL != c->name; c++) {
72
- if (Qundef == c->clas) { // indicates not defined
73
- continue;
74
- }
75
- if (Qnil == c->clas) {
76
- c->clas = path2class(c->name);
77
- }
78
- if (clas == c->clas) {
100
+ if (code_is(c, clas)) {
79
101
  if (NULL == c->decode) {
80
102
  break;
81
103
  }
@@ -89,13 +111,7 @@ void oj_code_set_active(Code codes, VALUE clas, bool active) {
89
111
  Code c = codes;
90
112
 
91
113
  for (; NULL != c->name; c++) {
92
- if (Qundef == c->clas) { // indicates not defined
93
- continue;
94
- }
95
- if (Qnil == c->clas) {
96
- c->clas = path2class(c->name);
97
- }
98
- if (clas == c->clas || Qnil == clas) {
114
+ if (Qnil == clas || code_is(c, clas)) {
99
115
  c->active = active;
100
116
  if (Qnil != clas) {
101
117
  break;
@@ -108,13 +124,7 @@ bool oj_code_has(Code codes, VALUE clas, bool encode) {
108
124
  Code c = codes;
109
125
 
110
126
  for (; NULL != c->name; c++) {
111
- if (Qundef == c->clas) { // indicates not defined
112
- continue;
113
- }
114
- if (Qnil == c->clas) {
115
- c->clas = path2class(c->name);
116
- }
117
- if (clas == c->clas) {
127
+ if (code_is(c, clas)) {
118
128
  if (encode) {
119
129
  return c->active && NULL != c->encode;
120
130
  } else {
data/ext/oj/code.h CHANGED
@@ -17,6 +17,7 @@ typedef struct _code {
17
17
  EncodeFunc encode;
18
18
  DecodeFunc decode;
19
19
  bool active; // For compat mode.
20
+ ID id; // Interned name, set on first lookup of a top-level class.
20
21
  } *Code;
21
22
 
22
23
  // Used by encode functions.
@@ -28,6 +29,7 @@ typedef struct _attr {
28
29
  VALUE time;
29
30
  } *Attr;
30
31
 
32
+ extern VALUE oj_code_class(Code c);
31
33
  extern bool oj_code_dump(Code codes, VALUE obj, int depth, Out out);
32
34
  extern VALUE oj_code_load(Code codes, VALUE clas, VALUE args);
33
35
  extern void oj_code_set_active(Code codes, VALUE clas, bool active);
data/ext/oj/compat.c CHANGED
@@ -159,18 +159,21 @@ static VALUE start_array(ParseInfo pi) {
159
159
  return rb_ary_new();
160
160
  }
161
161
 
162
+ void oj_compat_array_append_other(VALUE a, VALUE value) {
163
+ if (rb_cArray == rb_obj_class(a)) {
164
+ rb_ary_push(a, value);
165
+ } else {
166
+ // The array_class may not be an Array, and the json gem calls << even
167
+ // for Array subclasses.
168
+ rb_funcall(a, rb_intern("<<"), 1, value);
169
+ }
170
+ }
171
+
162
172
  static void array_append_num(ParseInfo pi, NumInfo ni) {
163
173
  Val parent = stack_peek(&pi->stack);
164
174
  volatile VALUE rval = oj_num_as_value(ni);
165
175
 
166
- if (!oj_use_array_alt && rb_cArray != rb_obj_class(parent->val)) {
167
- // The rb_ary_push would still work but the unit tests for the json
168
- // gem require the less efficient << method be called to push the
169
- // values.
170
- rb_funcall(parent->val, rb_intern("<<"), 1, rval);
171
- } else {
172
- rb_ary_push(parent->val, rval);
173
- }
176
+ oj_compat_array_append(parent->val, rval);
174
177
  TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, rval);
175
178
  }
176
179
 
@@ -181,26 +184,32 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
181
184
  VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
182
185
 
183
186
  if (Qnil != clas) {
184
- rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
187
+ oj_compat_array_append(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
185
188
  return;
186
189
  }
187
190
  }
188
- rb_ary_push(stack_peek(&pi->stack)->val, rstr);
191
+ oj_compat_array_append(stack_peek(&pi->stack)->val, rstr);
189
192
  TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, rstr);
190
193
  }
191
194
 
195
+ static void array_append_value(ParseInfo pi, VALUE value) {
196
+ oj_compat_array_append(stack_peek(&pi->stack)->val, value);
197
+ TRACE_PARSE_CALL(pi->options.trace, "append_value", pi, value);
198
+ }
199
+
192
200
  void oj_set_compat_callbacks(ParseInfo pi) {
193
201
  oj_set_strict_callbacks(pi);
194
- pi->start_hash = start_hash;
195
- pi->end_hash = end_hash;
196
- pi->hash_set_cstr = hash_set_cstr;
197
- pi->hash_set_num = hash_set_num;
198
- pi->hash_set_value = hash_set_value;
199
- pi->add_num = add_num;
200
- pi->add_cstr = add_cstr;
201
- pi->array_append_cstr = array_append_cstr;
202
- pi->start_array = start_array;
203
- pi->array_append_num = array_append_num;
202
+ pi->start_hash = start_hash;
203
+ pi->end_hash = end_hash;
204
+ pi->hash_set_cstr = hash_set_cstr;
205
+ pi->hash_set_num = hash_set_num;
206
+ pi->hash_set_value = hash_set_value;
207
+ pi->add_num = add_num;
208
+ pi->add_cstr = add_cstr;
209
+ pi->array_append_cstr = array_append_cstr;
210
+ pi->array_append_value = array_append_value;
211
+ pi->start_array = start_array;
212
+ pi->array_append_num = array_append_num;
204
213
  }
205
214
 
206
215
  VALUE
data/ext/oj/custom.c CHANGED
@@ -492,6 +492,7 @@ static VALUE dump_common(VALUE obj, int depth, Out out) {
492
492
  rs = rb_funcall2(obj, oj_to_json_id, out->argc, out->argv);
493
493
  }
494
494
  TRACE(out->opts->trace, "to_json", obj, depth + 1, TraceRubyOut);
495
+ StringValue(rs);
495
496
  s = RSTRING_PTR(rs);
496
497
  len = RSTRING_LEN(rs);
497
498
 
@@ -565,7 +566,7 @@ static int dump_attr_cb(ID key, VALUE value, VALUE ov) {
565
566
  } else if (Yes == out->opts->ignore_under && '@' == *attr && '_' == attr[1]) {
566
567
  return ST_CONTINUE;
567
568
  }
568
- if (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr)) {
569
+ if ('@' != *attr && (0 == strcmp("bt", attr) || 0 == strcmp("mesg", attr) || 0 == strcmp("bt_locations", attr))) {
569
570
  return ST_CONTINUE;
570
571
  }
571
572
  assure_size(out, size);
@@ -707,6 +708,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
707
708
  cnt--;
708
709
  for (i = 0; i <= cnt; i++) {
709
710
  if (out->opts->dump_opts.use) {
711
+ assure_size(out, size);
710
712
  if (0 < out->opts->dump_opts.array_size) {
711
713
  APPEND_CHARS(out->cur, out->opts->dump_opts.array_nl, out->opts->dump_opts.array_size);
712
714
  }
@@ -724,7 +726,7 @@ static void dump_array(VALUE a, int depth, Out out, bool as_ok) {
724
726
  *out->cur++ = ',';
725
727
  }
726
728
  }
727
- size = depth * out->indent + 1;
729
+ size = indent_len(out, depth, out->opts->dump_opts.array_size) + 1;
728
730
  assure_size(out, size);
729
731
  if (out->opts->dump_opts.use) {
730
732
  if (0 < out->opts->dump_opts.array_size) {
@@ -816,6 +818,18 @@ static void dump_struct(VALUE obj, int depth, Out out, bool as_ok) {
816
818
  oj_dump_custom_val(v, d3, out, true);
817
819
  *out->cur++ = ',';
818
820
  }
821
+ if (2 == cnt && rb_obj_is_kind_of(obj, rb_cRange)) {
822
+ VALUE beg;
823
+ VALUE end;
824
+ int excl;
825
+
826
+ rb_range_values(obj, &beg, &end, &excl);
827
+ assure_size(out, size + 7);
828
+ fill_indent(out, d3);
829
+ APPEND_CHARS(out->cur, "\"excl\":", 7);
830
+ oj_dump_custom_val(excl ? Qtrue : Qfalse, d3, out, true);
831
+ *out->cur++ = ',';
832
+ }
819
833
  out->cur--;
820
834
  *out->cur++ = '}';
821
835
  *out->cur = '\0';
@@ -904,6 +918,9 @@ static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, c
904
918
  if (Qundef == kval->key_val && Yes == pi->options.create_ok && NULL != pi->options.create_id &&
905
919
  *pi->options.create_id == *key && (size_t)pi->options.create_id_len == klen &&
906
920
  0 == strncmp(pi->options.create_id, key, klen)) {
921
+ if (oj_is_backtrace_name(str, len)) {
922
+ return;
923
+ }
907
924
  parent->clas = oj_name2class(pi, str, len, false, rb_eArgError);
908
925
  if (2 == klen && '^' == *key && 'o' == key[1]) {
909
926
  if (Qundef != parent->clas) {
@@ -1014,7 +1031,7 @@ static void array_append_num(ParseInfo pi, NumInfo ni) {
1014
1031
  Val parent = stack_peek(&pi->stack);
1015
1032
  volatile VALUE rval = oj_num_as_value(ni);
1016
1033
 
1017
- rb_ary_push(parent->val, rval);
1034
+ oj_compat_array_append(parent->val, rval);
1018
1035
  TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, rval);
1019
1036
  }
1020
1037
 
@@ -1025,11 +1042,11 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
1025
1042
  VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
1026
1043
 
1027
1044
  if (Qnil != clas) {
1028
- rb_ary_push(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
1045
+ oj_compat_array_append(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
1029
1046
  return;
1030
1047
  }
1031
1048
  }
1032
- rb_ary_push(stack_peek(&pi->stack)->val, rstr);
1049
+ oj_compat_array_append(stack_peek(&pi->stack)->val, rstr);
1033
1050
  TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, rstr);
1034
1051
  }
1035
1052