oj 3.17.6 → 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 +4 -4
- data/CHANGELOG.md +67 -0
- data/ext/oj/cache.c +64 -47
- data/ext/oj/cache.h +1 -1
- data/ext/oj/code.c +38 -28
- data/ext/oj/code.h +2 -0
- data/ext/oj/compat.c +61 -29
- data/ext/oj/custom.c +38 -5
- data/ext/oj/dump.c +93 -29
- data/ext/oj/dump_compat.c +8 -11
- data/ext/oj/dump_object.c +18 -2
- data/ext/oj/dump_strict.c +2 -1
- data/ext/oj/extconf.rb +7 -1
- data/ext/oj/fast.c +46 -29
- data/ext/oj/intern.c +75 -77
- data/ext/oj/mimic_json.c +5 -29
- data/ext/oj/object.c +21 -2
- data/ext/oj/oj.c +136 -33
- data/ext/oj/oj.h +9 -7
- data/ext/oj/parse.c +58 -4
- data/ext/oj/parse.h +23 -1
- data/ext/oj/parser.c +99 -36
- data/ext/oj/parser.h +1 -1
- data/ext/oj/rails.c +8 -3
- data/ext/oj/reader.c +4 -0
- data/ext/oj/reader.h +1 -0
- data/ext/oj/resolve.c +5 -4
- data/ext/oj/resolve.h +9 -0
- data/ext/oj/saj.c +14 -7
- data/ext/oj/saj2.c +44 -28
- data/ext/oj/simd.h +5 -3
- data/ext/oj/sparse.c +7 -0
- data/ext/oj/stream_writer.c +3 -0
- data/ext/oj/strict.c +42 -4
- data/ext/oj/string_writer.c +17 -3
- data/ext/oj/usual.c +12 -4
- data/ext/oj/val_stack.c +11 -22
- data/ext/oj/val_stack.h +45 -24
- data/ext/oj/wab.c +3 -0
- data/lib/oj/generic_object.rb +82 -0
- data/lib/oj/json.rb +1 -71
- data/lib/oj/mimic.rb +0 -1
- data/lib/oj/version.rb +1 -1
- metadata +16 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 887bd6056f15f0ed95de347bc87d12c00c2de9a071b972f16be14952c408f6a6
|
|
4
|
+
data.tar.gz: af830ec844053ce4c955ac193260d53f5edf88159038c30e5c047b8fd9b334d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1aa3a234183cfd1e86b6a2b08f91ec553a6048405042ca887647e43198a08ab06287329389bb0c0d4f6c73a637eff158e29da7f187ae4778ebab7379984c771d
|
|
7
|
+
data.tar.gz: 6e1604f05a6b48364549b350c9b18c4e9b1c5dd819881d82e172ee466ce9c10ff49fbb60309f56ec951ce99f0c2791ca31b94a12e23ebef2bde40c0d44a0a8c4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
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
|
+
|
|
57
|
+
## 3.17.7 - 2026-09-29
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- issue #1104, where `Oj.dump` in compat and custom mode forwarded its whole options hash to the `to_json` methods it calls, so with json 3.0, whose generator raises `ArgumentError` on options it does not know, dumping a `Time` or any other object whose `to_json` comes from the json gem failed with `unknown keywords: mode, use_to_json`. Nested values reach `to_json` even without `use_to_json`, so `Oj.dump(data, mode: :compat)` failed the same way for data holding a `BigDecimal` or `Time`. The options Oj acts on are now dropped from the hash `to_json` receives, except `:only` and `:except`, which ActiveSupport's `as_json` reads. Keys Oj does not know are passed through untouched.
|
|
61
|
+
- Speed up hash-heavy documents: skip the 11KB per-parse memset and bulk-insert hashes (#1096)
|
|
62
|
+
- Preserve decimal placement for large parser numbers (#1099)
|
|
63
|
+
- Keep zero digits in oversized parser exponents (#1100)
|
|
64
|
+
- Parse very small usual-mode numbers correctly (#1101)
|
|
65
|
+
- Speed up symbol keys by interning without a temporary String (#1102)
|
|
66
|
+
- Keep Oj's own options out of the hash passed to to_json (#1106)
|
|
67
|
+
- Ensure class-cache mutex is released on exceptions (#1108)
|
|
68
|
+
- Guard __has_include with an #ifdef to support older GCC versions (4.4.7 etc.) (#1111)
|
|
69
|
+
|
|
3
70
|
## 3.17.6 - 2026-08-10
|
|
4
71
|
|
|
5
72
|
- Fixed issue #1092, where the encoder ActiveSupport 8.1 caches with `escape: false` froze the options as they stood before `set_encoder` wrote `time_precision` into them, so `to_json(escape: false)` emitted 9 fractional digits. An options hash that names no option Oj knows no longer detaches an encoder from the defaults. (#1093)
|
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
|
-
|
|
29
|
-
#define
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
79
|
-
const uint8_t *end
|
|
80
|
-
|
|
81
|
-
uint64_t
|
|
82
|
-
uint64_t
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
102
|
-
|
|
115
|
+
for (i = 0; i < (len & 7); i++) {
|
|
116
|
+
b |= (uint64_t)key[i] << (8 * i);
|
|
103
117
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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 =
|
|
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 ==
|
|
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 =
|
|
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 ==
|
|
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 =
|
|
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
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
@@ -33,7 +33,10 @@ static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, c
|
|
|
33
33
|
rstr = rb_funcall(clas, oj_json_create_id, 1, rstr);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
if (
|
|
36
|
+
if (Qnil == pi->options.hash_class) {
|
|
37
|
+
stack_pair_push(&pi->stack, rkey, rstr);
|
|
38
|
+
parent->pcnt += 2;
|
|
39
|
+
} else if (rb_cHash != rb_obj_class(parent->val)) {
|
|
37
40
|
// The rb_hash_set would still work but the unit tests for the
|
|
38
41
|
// json gem require the less efficient []= method be called to set
|
|
39
42
|
// values. Even using the store method to set the values will fail
|
|
@@ -47,20 +50,34 @@ static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, c
|
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
static VALUE start_hash(ParseInfo pi) {
|
|
50
|
-
volatile VALUE h;
|
|
51
|
-
|
|
52
53
|
if (Qnil != pi->options.hash_class) {
|
|
53
|
-
|
|
54
|
-
} else {
|
|
55
|
-
h = rb_hash_new();
|
|
54
|
+
return rb_class_new_instance(0, NULL, pi->options.hash_class);
|
|
56
55
|
}
|
|
57
56
|
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
|
|
58
|
-
|
|
57
|
+
// The Hash is built at end_hash from the buffered pairs.
|
|
58
|
+
return Qundef;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
static void end_hash(struct _parseInfo *pi) {
|
|
62
62
|
Val parent = stack_peek(&pi->stack);
|
|
63
63
|
|
|
64
|
+
if (Qnil == pi->options.hash_class) {
|
|
65
|
+
ValStack stack = &pi->stack;
|
|
66
|
+
size_t cnt = parent->pcnt;
|
|
67
|
+
volatile VALUE h;
|
|
68
|
+
|
|
69
|
+
#if HAVE_RB_HASH_NEW_CAPA
|
|
70
|
+
h = rb_hash_new_capa((long)(cnt / 2));
|
|
71
|
+
#else
|
|
72
|
+
h = rb_hash_new();
|
|
73
|
+
#endif
|
|
74
|
+
if (0 < cnt) {
|
|
75
|
+
rb_hash_bulk_insert((long)cnt, stack->pairs + stack->pcnt - cnt, h);
|
|
76
|
+
stack->pcnt -= cnt;
|
|
77
|
+
parent->pcnt = 0;
|
|
78
|
+
}
|
|
79
|
+
parent->val = h;
|
|
80
|
+
}
|
|
64
81
|
if (0 != parent->classname) {
|
|
65
82
|
volatile VALUE clas;
|
|
66
83
|
|
|
@@ -103,7 +120,10 @@ static void add_num(ParseInfo pi, NumInfo ni) {
|
|
|
103
120
|
static void hash_set_num(struct _parseInfo *pi, Val parent, NumInfo ni) {
|
|
104
121
|
volatile VALUE rval = oj_num_as_value(ni);
|
|
105
122
|
|
|
106
|
-
if (
|
|
123
|
+
if (Qnil == pi->options.hash_class) {
|
|
124
|
+
stack_pair_push(&pi->stack, oj_calc_hash_key(pi, parent), rval);
|
|
125
|
+
parent->pcnt += 2;
|
|
126
|
+
} else if (rb_cHash != rb_obj_class(parent->val)) {
|
|
107
127
|
// The rb_hash_set would still work but the unit tests for the
|
|
108
128
|
// json gem require the less efficient []= method be called to set
|
|
109
129
|
// values. Even using the store method to set the values will fail
|
|
@@ -116,7 +136,10 @@ static void hash_set_num(struct _parseInfo *pi, Val parent, NumInfo ni) {
|
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
static void hash_set_value(ParseInfo pi, Val parent, VALUE value) {
|
|
119
|
-
if (
|
|
139
|
+
if (Qnil == pi->options.hash_class) {
|
|
140
|
+
stack_pair_push(&pi->stack, oj_calc_hash_key(pi, parent), value);
|
|
141
|
+
parent->pcnt += 2;
|
|
142
|
+
} else if (rb_cHash != rb_obj_class(parent->val)) {
|
|
120
143
|
// The rb_hash_set would still work but the unit tests for the
|
|
121
144
|
// json gem require the less efficient []= method be called to set
|
|
122
145
|
// values. Even using the store method to set the values will fail
|
|
@@ -136,18 +159,21 @@ static VALUE start_array(ParseInfo pi) {
|
|
|
136
159
|
return rb_ary_new();
|
|
137
160
|
}
|
|
138
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
|
+
|
|
139
172
|
static void array_append_num(ParseInfo pi, NumInfo ni) {
|
|
140
173
|
Val parent = stack_peek(&pi->stack);
|
|
141
174
|
volatile VALUE rval = oj_num_as_value(ni);
|
|
142
175
|
|
|
143
|
-
|
|
144
|
-
// The rb_ary_push would still work but the unit tests for the json
|
|
145
|
-
// gem require the less efficient << method be called to push the
|
|
146
|
-
// values.
|
|
147
|
-
rb_funcall(parent->val, rb_intern("<<"), 1, rval);
|
|
148
|
-
} else {
|
|
149
|
-
rb_ary_push(parent->val, rval);
|
|
150
|
-
}
|
|
176
|
+
oj_compat_array_append(parent->val, rval);
|
|
151
177
|
TRACE_PARSE_CALL(pi->options.trace, "append_number", pi, rval);
|
|
152
178
|
}
|
|
153
179
|
|
|
@@ -158,26 +184,32 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
|
|
|
158
184
|
VALUE clas = oj_rxclass_match(&pi->options.str_rx, str, len);
|
|
159
185
|
|
|
160
186
|
if (Qnil != clas) {
|
|
161
|
-
|
|
187
|
+
oj_compat_array_append(stack_peek(&pi->stack)->val, rb_funcall(clas, oj_json_create_id, 1, rstr));
|
|
162
188
|
return;
|
|
163
189
|
}
|
|
164
190
|
}
|
|
165
|
-
|
|
191
|
+
oj_compat_array_append(stack_peek(&pi->stack)->val, rstr);
|
|
166
192
|
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, rstr);
|
|
167
193
|
}
|
|
168
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
|
+
|
|
169
200
|
void oj_set_compat_callbacks(ParseInfo pi) {
|
|
170
201
|
oj_set_strict_callbacks(pi);
|
|
171
|
-
pi->start_hash
|
|
172
|
-
pi->end_hash
|
|
173
|
-
pi->hash_set_cstr
|
|
174
|
-
pi->hash_set_num
|
|
175
|
-
pi->hash_set_value
|
|
176
|
-
pi->add_num
|
|
177
|
-
pi->add_cstr
|
|
178
|
-
pi->array_append_cstr
|
|
179
|
-
pi->
|
|
180
|
-
pi->
|
|
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;
|
|
181
213
|
}
|
|
182
214
|
|
|
183
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
|
|
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
|
-
|
|
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,16 +1042,32 @@ 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
|
-
|
|
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
|
-
|
|
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
|
|
|
1053
|
+
// Compat mode defers Hash creation to its end_hash (bulk insert from the
|
|
1054
|
+
// buffered pairs) but the custom callbacks below fill the Hash as they go,
|
|
1055
|
+
// so hashes must exist from the opening brace here.
|
|
1056
|
+
static VALUE start_hash(ParseInfo pi) {
|
|
1057
|
+
volatile VALUE h;
|
|
1058
|
+
|
|
1059
|
+
if (Qnil != pi->options.hash_class) {
|
|
1060
|
+
h = rb_class_new_instance(0, NULL, pi->options.hash_class);
|
|
1061
|
+
} else {
|
|
1062
|
+
h = rb_hash_new();
|
|
1063
|
+
}
|
|
1064
|
+
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
|
|
1065
|
+
return h;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1036
1068
|
void oj_set_custom_callbacks(ParseInfo pi) {
|
|
1037
1069
|
oj_set_compat_callbacks(pi);
|
|
1070
|
+
pi->start_hash = start_hash;
|
|
1038
1071
|
pi->hash_set_cstr = hash_set_cstr;
|
|
1039
1072
|
pi->end_hash = end_hash;
|
|
1040
1073
|
pi->hash_set_num = hash_set_num;
|