oj 3.17.5 → 3.17.7
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 +17 -0
- data/ext/oj/compat.c +32 -9
- data/ext/oj/custom.c +16 -0
- data/ext/oj/extconf.rb +7 -0
- data/ext/oj/intern.c +67 -40
- data/ext/oj/mimic_json.c +1 -1
- data/ext/oj/oj.c +91 -15
- data/ext/oj/oj.h +8 -6
- data/ext/oj/parse.c +5 -1
- data/ext/oj/parse.h +14 -1
- data/ext/oj/parser.c +42 -20
- data/ext/oj/parser.h +1 -1
- data/ext/oj/rails.c +20 -8
- data/ext/oj/simd.h +4 -0
- data/ext/oj/strict.c +42 -4
- data/ext/oj/usual.c +8 -3
- data/ext/oj/val_stack.c +11 -0
- data/ext/oj/val_stack.h +54 -3
- data/lib/oj/version.rb +1 -1
- data/pages/Rails.md +4 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d8e7bd60ca653140f1d101a88ded37ad5b6fe2af3d5e01ae6787435238c75eb
|
|
4
|
+
data.tar.gz: 9ef057d4e77c5b2ba30f9a34b2fc3621875c902ca05da25495a4abf657f0bbdb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9654f35d338c316388534ee7d7143165dd2aab82960324f7fd313771445a2dc2d2b22964408b533735dab30e4fd554b5f8abe272a7b3beb5352720cd039fb16
|
|
7
|
+
data.tar.gz: 4ffc5a78fc866f6824564d34e2db316c56454999bdcb0f91068b40e4dd28f04f23acfb9ce819364bb6a33c3aa5c2e3be0979982220552a1ec3a5eceedfd6f320
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 3.17.7 - 2026-09-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- 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.
|
|
7
|
+
- Speed up hash-heavy documents: skip the 11KB per-parse memset and bulk-insert hashes (#1096)
|
|
8
|
+
- Preserve decimal placement for large parser numbers (#1099)
|
|
9
|
+
- Keep zero digits in oversized parser exponents (#1100)
|
|
10
|
+
- Parse very small usual-mode numbers correctly (#1101)
|
|
11
|
+
- Speed up symbol keys by interning without a temporary String (#1102)
|
|
12
|
+
- Keep Oj's own options out of the hash passed to to_json (#1106)
|
|
13
|
+
- Ensure class-cache mutex is released on exceptions (#1108)
|
|
14
|
+
- Guard __has_include with an #ifdef to support older GCC versions (4.4.7 etc.) (#1111)
|
|
15
|
+
|
|
16
|
+
## 3.17.6 - 2026-08-10
|
|
17
|
+
|
|
18
|
+
- 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)
|
|
19
|
+
|
|
3
20
|
## 3.17.5 - 2026-07-31
|
|
4
21
|
|
|
5
22
|
Most of this release comes out of a security review of the C extension. There are no API changes.
|
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
|
data/ext/oj/custom.c
CHANGED
|
@@ -1033,8 +1033,24 @@ static void array_append_cstr(ParseInfo pi, const char *str, size_t len, const c
|
|
|
1033
1033
|
TRACE_PARSE_CALL(pi->options.trace, "append_string", pi, rstr);
|
|
1034
1034
|
}
|
|
1035
1035
|
|
|
1036
|
+
// Compat mode defers Hash creation to its end_hash (bulk insert from the
|
|
1037
|
+
// buffered pairs) but the custom callbacks below fill the Hash as they go,
|
|
1038
|
+
// so hashes must exist from the opening brace here.
|
|
1039
|
+
static VALUE start_hash(ParseInfo pi) {
|
|
1040
|
+
volatile VALUE h;
|
|
1041
|
+
|
|
1042
|
+
if (Qnil != pi->options.hash_class) {
|
|
1043
|
+
h = rb_class_new_instance(0, NULL, pi->options.hash_class);
|
|
1044
|
+
} else {
|
|
1045
|
+
h = rb_hash_new();
|
|
1046
|
+
}
|
|
1047
|
+
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
|
|
1048
|
+
return h;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1036
1051
|
void oj_set_custom_callbacks(ParseInfo pi) {
|
|
1037
1052
|
oj_set_compat_callbacks(pi);
|
|
1053
|
+
pi->start_hash = start_hash;
|
|
1038
1054
|
pi->hash_set_cstr = hash_set_cstr;
|
|
1039
1055
|
pi->end_hash = end_hash;
|
|
1040
1056
|
pi->hash_set_num = hash_set_num;
|
data/ext/oj/extconf.rb
CHANGED
|
@@ -31,6 +31,7 @@ have_func('stpcpy')
|
|
|
31
31
|
have_func('pthread_mutex_init')
|
|
32
32
|
have_func('getrlimit', 'sys/resource.h')
|
|
33
33
|
have_func('rb_enc_interned_str')
|
|
34
|
+
have_func('rb_hash_new_capa', 'ruby.h')
|
|
34
35
|
have_func('rb_ext_ractor_safe', 'ruby.h')
|
|
35
36
|
have_func('rb_hash_start', 'ruby.h')
|
|
36
37
|
|
|
@@ -48,6 +49,12 @@ if try_cflags('-msse2')
|
|
|
48
49
|
$CPPFLAGS += ' -msse2'
|
|
49
50
|
end
|
|
50
51
|
|
|
52
|
+
# Ensure C99 mode for compilers that default to C90 (e.g. GCC 4.x).
|
|
53
|
+
# simd.h uses C99 for-loop declarations that fail under -std=gnu90.
|
|
54
|
+
if !$CFLAGS.include?('-std=') && try_cflags('-std=gnu99')
|
|
55
|
+
$CFLAGS += ' -std=gnu99'
|
|
56
|
+
end
|
|
57
|
+
|
|
51
58
|
if enable_config('trace-log', false)
|
|
52
59
|
dflags['OJ_ENABLE_TRACE_LOG'] = 1
|
|
53
60
|
end
|
data/ext/oj/intern.c
CHANGED
|
@@ -50,7 +50,12 @@ static VALUE form_str(const char *str, size_t len) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
static VALUE form_sym(const char *str, size_t len) {
|
|
53
|
-
|
|
53
|
+
VALUE sym = rb_check_symbol_cstr(str, (long)len, oj_utf8_encoding);
|
|
54
|
+
|
|
55
|
+
if (Qnil == sym) {
|
|
56
|
+
sym = rb_str_intern(rb_utf8_str_new(str, len));
|
|
57
|
+
}
|
|
58
|
+
return sym;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
static VALUE form_attr(const char *str, size_t len) {
|
|
@@ -243,61 +248,83 @@ static VALUE resolve_classpath(ParseInfo pi, const char *name, size_t len, int a
|
|
|
243
248
|
return clas;
|
|
244
249
|
}
|
|
245
250
|
|
|
251
|
+
typedef struct _internArgs {
|
|
252
|
+
KeyVal bucket; // last bucket in the chain for the key's slot
|
|
253
|
+
const char *key;
|
|
254
|
+
size_t len;
|
|
255
|
+
ParseInfo pi;
|
|
256
|
+
int auto_define;
|
|
257
|
+
VALUE error_class;
|
|
258
|
+
} *InternArgs;
|
|
259
|
+
|
|
260
|
+
// Resolves the class and only then adds it to the cache so a raise while
|
|
261
|
+
// resolving does not leave a key cached with no value.
|
|
262
|
+
static VALUE intern_new_class(VALUE a) {
|
|
263
|
+
InternArgs args = (InternArgs)a;
|
|
264
|
+
KeyVal bucket = args->bucket;
|
|
265
|
+
VALUE clas = resolve_classpath(args->pi, args->key, args->len, args->auto_define, args->error_class);
|
|
266
|
+
|
|
267
|
+
if (NULL != bucket->key) { // not the top slot
|
|
268
|
+
KeyVal b = OJ_R_ALLOC(struct _keyVal);
|
|
269
|
+
|
|
270
|
+
b->next = NULL;
|
|
271
|
+
bucket->next = b;
|
|
272
|
+
bucket = b;
|
|
273
|
+
}
|
|
274
|
+
bucket->key = oj_strndup(args->key, args->len);
|
|
275
|
+
bucket->len = args->len;
|
|
276
|
+
bucket->val = clas;
|
|
277
|
+
|
|
278
|
+
return clas;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
static KeyVal find_class(KeyVal bucket, const char *key, size_t len, VALUE *clas) {
|
|
282
|
+
if (NULL != bucket->key) { // not the top slot
|
|
283
|
+
for (KeyVal b = bucket; 0 != b; b = b->next) {
|
|
284
|
+
if (len == b->len && 0 == strncmp(b->key, key, len)) {
|
|
285
|
+
*clas = b->val;
|
|
286
|
+
return NULL;
|
|
287
|
+
}
|
|
288
|
+
bucket = b;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return bucket;
|
|
292
|
+
}
|
|
293
|
+
|
|
246
294
|
VALUE oj_class_intern(const char *key, size_t len, bool safe, ParseInfo pi, int auto_define, VALUE error_class) {
|
|
247
|
-
uint64_t
|
|
248
|
-
|
|
249
|
-
|
|
295
|
+
uint64_t h = hash_calc((const uint8_t *)key, len) & HASH_MASK;
|
|
296
|
+
struct _internArgs args = {NULL, key, len, pi, auto_define, error_class};
|
|
297
|
+
VALUE clas = Qundef;
|
|
250
298
|
|
|
251
299
|
if (safe) {
|
|
300
|
+
int state = 0;
|
|
301
|
+
|
|
252
302
|
#if HAVE_PTHREAD_MUTEX_INIT
|
|
253
303
|
pthread_mutex_lock(&class_hash.mutex);
|
|
254
304
|
#else
|
|
255
305
|
rb_mutex_lock(class_hash.mutex);
|
|
256
306
|
#endif
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
#else
|
|
263
|
-
rb_mutex_unlock(class_hash.mutex);
|
|
264
|
-
#endif
|
|
265
|
-
return b->val;
|
|
266
|
-
}
|
|
267
|
-
bucket = b;
|
|
268
|
-
}
|
|
269
|
-
b = OJ_R_ALLOC(struct _keyVal);
|
|
270
|
-
b->next = NULL;
|
|
271
|
-
bucket->next = b;
|
|
272
|
-
bucket = b;
|
|
307
|
+
// Resolving calls into Ruby and allocating, either of which can
|
|
308
|
+
// raise and unwind past the unlock, so the work is protected and any
|
|
309
|
+
// exception rethrown after the mutex is released.
|
|
310
|
+
if (NULL != (args.bucket = find_class(class_hash.slots + h, key, len, &clas))) {
|
|
311
|
+
clas = rb_protect(intern_new_class, (VALUE)&args, &state);
|
|
273
312
|
}
|
|
274
|
-
bucket->key = oj_strndup(key, len);
|
|
275
|
-
bucket->len = len;
|
|
276
|
-
bucket->val = resolve_classpath(pi, key, len, auto_define, error_class);
|
|
277
313
|
#if HAVE_PTHREAD_MUTEX_INIT
|
|
278
314
|
pthread_mutex_unlock(&class_hash.mutex);
|
|
279
315
|
#else
|
|
280
316
|
rb_mutex_unlock(class_hash.mutex);
|
|
281
317
|
#endif
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
for (b = bucket; 0 != b; b = b->next) {
|
|
285
|
-
if (len == b->len && 0 == strncmp(b->key, key, len)) {
|
|
286
|
-
return (ID)b->val;
|
|
287
|
-
}
|
|
288
|
-
bucket = b;
|
|
289
|
-
}
|
|
290
|
-
b = OJ_R_ALLOC(struct _keyVal);
|
|
291
|
-
b->next = NULL;
|
|
292
|
-
bucket->next = b;
|
|
293
|
-
bucket = b;
|
|
318
|
+
if (0 != state) {
|
|
319
|
+
rb_jump_tag(state);
|
|
294
320
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
bucket->val = resolve_classpath(pi, key, len, auto_define, error_class);
|
|
321
|
+
} else if (NULL != (args.bucket = find_class(class_hash.slots + h, key, len, &clas))) {
|
|
322
|
+
clas = intern_new_class((VALUE)&args);
|
|
298
323
|
}
|
|
299
|
-
|
|
300
|
-
|
|
324
|
+
if (NULL != args.bucket) { // newly added to the cache
|
|
325
|
+
rb_gc_register_mark_object(clas);
|
|
326
|
+
}
|
|
327
|
+
return clas;
|
|
301
328
|
}
|
|
302
329
|
|
|
303
330
|
char *oj_strndup(const char *s, size_t len) {
|
data/ext/oj/mimic_json.c
CHANGED
|
@@ -461,7 +461,7 @@ oj_mimic_pretty_generate(int argc, VALUE *argv, VALUE self) {
|
|
|
461
461
|
if (1 == argc || Qnil == argv[1]) {
|
|
462
462
|
h = rb_hash_new();
|
|
463
463
|
} else {
|
|
464
|
-
h = argv[1];
|
|
464
|
+
h = rb_hash_dup(argv[1]);
|
|
465
465
|
}
|
|
466
466
|
if (!oj_hash_has_key(h, oj_indent_sym)) {
|
|
467
467
|
rb_hash_aset(h, oj_indent_sym, rb_str_new2(" "));
|
data/ext/oj/oj.c
CHANGED
|
@@ -836,12 +836,14 @@ static const char *make_only_value(VALUE v) {
|
|
|
836
836
|
return NULL;
|
|
837
837
|
}
|
|
838
838
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
// Apply one key and value to copts. Returns true if the key named an option Oj
|
|
840
|
+
// knows and false if it did not. An unrecognized key is still ignored rather
|
|
841
|
+
// than an error; the return only lets a caller tell the two cases apart.
|
|
842
|
+
static bool parse_one_option(VALUE k, VALUE v, Options copts) {
|
|
843
|
+
size_t len;
|
|
842
844
|
|
|
843
845
|
if (set_yesno_options(k, v, copts)) {
|
|
844
|
-
return
|
|
846
|
+
return true;
|
|
845
847
|
}
|
|
846
848
|
if (oj_indent_sym == k) {
|
|
847
849
|
switch (rb_type(v)) {
|
|
@@ -967,7 +969,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
967
969
|
}
|
|
968
970
|
} else if (bigdecimal_load_sym == k) {
|
|
969
971
|
if (Qnil == v) {
|
|
970
|
-
return
|
|
972
|
+
return true;
|
|
971
973
|
}
|
|
972
974
|
|
|
973
975
|
if (bigdecimal_sym == v || Qtrue == v) {
|
|
@@ -983,7 +985,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
983
985
|
}
|
|
984
986
|
} else if (compat_bigdecimal_sym == k) {
|
|
985
987
|
if (Qnil == v) {
|
|
986
|
-
return
|
|
988
|
+
return true;
|
|
987
989
|
}
|
|
988
990
|
copts->compat_bigdec = (Qtrue == v);
|
|
989
991
|
} else if (oj_decimal_class_sym == k) {
|
|
@@ -1078,7 +1080,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1078
1080
|
}
|
|
1079
1081
|
} else if (nan_sym == k) {
|
|
1080
1082
|
if (Qnil == v) {
|
|
1081
|
-
return
|
|
1083
|
+
return true;
|
|
1082
1084
|
}
|
|
1083
1085
|
if (null_sym == v) {
|
|
1084
1086
|
copts->dump_opts.nan_dump = NullNan;
|
|
@@ -1095,7 +1097,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1095
1097
|
}
|
|
1096
1098
|
} else if (omit_nil_sym == k) {
|
|
1097
1099
|
if (Qnil == v) {
|
|
1098
|
-
return
|
|
1100
|
+
return true;
|
|
1099
1101
|
}
|
|
1100
1102
|
if (Qtrue == v) {
|
|
1101
1103
|
copts->dump_opts.omit_nil = true;
|
|
@@ -1106,7 +1108,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1106
1108
|
}
|
|
1107
1109
|
} else if (omit_null_byte_sym == k) {
|
|
1108
1110
|
if (Qnil == v) {
|
|
1109
|
-
return
|
|
1111
|
+
return true;
|
|
1110
1112
|
}
|
|
1111
1113
|
if (Qtrue == v) {
|
|
1112
1114
|
copts->dump_opts.omit_null_byte = true;
|
|
@@ -1167,7 +1169,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1167
1169
|
}
|
|
1168
1170
|
} else if (integer_range_sym == k) {
|
|
1169
1171
|
if (Qnil == v) {
|
|
1170
|
-
return
|
|
1172
|
+
return true;
|
|
1171
1173
|
}
|
|
1172
1174
|
if (rb_obj_class(v) == rb_cRange) {
|
|
1173
1175
|
VALUE min = rb_funcall(v, oj_begin_id, 0);
|
|
@@ -1198,7 +1200,7 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1198
1200
|
}
|
|
1199
1201
|
} else if (symbol_keys_sym == k || oj_symbolize_names_sym == k) {
|
|
1200
1202
|
if (Qnil == v) {
|
|
1201
|
-
return
|
|
1203
|
+
return true;
|
|
1202
1204
|
}
|
|
1203
1205
|
copts->sym_key = (Qtrue == v) ? Yes : No;
|
|
1204
1206
|
|
|
@@ -1233,21 +1235,90 @@ static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
1233
1235
|
OJ_R_FREE((void *)copts->dump_opts.except);
|
|
1234
1236
|
}
|
|
1235
1237
|
copts->dump_opts.except = make_only_value(v);
|
|
1238
|
+
} else {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
return true;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Collects the results of parse_one_option() over every key in a hash.
|
|
1245
|
+
struct _optsParse {
|
|
1246
|
+
Options copts;
|
|
1247
|
+
bool consumed; // some key was an Oj option
|
|
1248
|
+
VALUE orig; // the caller's hash, never modified
|
|
1249
|
+
VALUE rest; // orig, or a copy of it without the Oj options
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
static void drop_from_rest(struct _optsParse *op, VALUE k) {
|
|
1253
|
+
if (op->rest == op->orig) {
|
|
1254
|
+
op->rest = rb_hash_dup(op->orig);
|
|
1255
|
+
}
|
|
1256
|
+
rb_hash_delete(op->rest, k);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
1260
|
+
struct _optsParse *op = (struct _optsParse *)opts;
|
|
1261
|
+
|
|
1262
|
+
if (parse_one_option(k, v, op->copts)) {
|
|
1263
|
+
op->consumed = true;
|
|
1264
|
+
// :only and :except are also read by ActiveSupport's as_json, which
|
|
1265
|
+
// compat mode reaches through to_json, so they are left in.
|
|
1266
|
+
if (only_sym != k && except_sym != k) {
|
|
1267
|
+
drop_from_rest(op, k);
|
|
1268
|
+
}
|
|
1236
1269
|
}
|
|
1237
1270
|
return ST_CONTINUE;
|
|
1238
1271
|
}
|
|
1239
1272
|
|
|
1240
|
-
void
|
|
1273
|
+
static void parse_options(VALUE ropts, Options copts, struct _optsParse *op) {
|
|
1274
|
+
op->copts = copts;
|
|
1275
|
+
op->consumed = false;
|
|
1276
|
+
op->orig = ropts;
|
|
1277
|
+
op->rest = ropts;
|
|
1278
|
+
|
|
1241
1279
|
if (T_HASH != rb_type(ropts)) {
|
|
1242
1280
|
return;
|
|
1243
1281
|
}
|
|
1244
|
-
rb_hash_foreach(ropts, parse_options_cb, (VALUE)
|
|
1282
|
+
rb_hash_foreach(ropts, parse_options_cb, (VALUE)op);
|
|
1283
|
+
if (Qnil != rb_hash_lookup(ropts, match_string_sym)) {
|
|
1284
|
+
op->consumed = true;
|
|
1285
|
+
drop_from_rest(op, match_string_sym);
|
|
1286
|
+
}
|
|
1245
1287
|
oj_parse_opt_match_string(&copts->str_rx, ropts);
|
|
1246
1288
|
|
|
1247
1289
|
copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size ||
|
|
1248
1290
|
0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size ||
|
|
1249
1291
|
0 < copts->dump_opts.array_size);
|
|
1250
|
-
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// Apply the options in ropts to copts and report whether any key was
|
|
1295
|
+
// recognized. A hash of nothing but unknown keys leaves copts untouched and
|
|
1296
|
+
// returns false.
|
|
1297
|
+
bool oj_parse_options_consumed(VALUE ropts, Options copts) {
|
|
1298
|
+
struct _optsParse op;
|
|
1299
|
+
|
|
1300
|
+
parse_options(ropts, copts, &op);
|
|
1301
|
+
|
|
1302
|
+
return op.consumed;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Apply the options in ropts to copts and return what is left for the
|
|
1306
|
+
// to_json and as_json methods that Oj.dump() calls: ropts itself when it
|
|
1307
|
+
// holds no Oj option, otherwise a copy without them. The json gem's
|
|
1308
|
+
// generator raises on options it does not know as of json 3.0, so Oj's own
|
|
1309
|
+
// must not reach it. ropts is never modified.
|
|
1310
|
+
VALUE oj_parse_options_rest(VALUE ropts, Options copts) {
|
|
1311
|
+
struct _optsParse op;
|
|
1312
|
+
|
|
1313
|
+
parse_options(ropts, copts, &op);
|
|
1314
|
+
|
|
1315
|
+
return op.rest;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
void oj_parse_options(VALUE ropts, Options copts) {
|
|
1319
|
+
struct _optsParse op;
|
|
1320
|
+
|
|
1321
|
+
parse_options(ropts, copts, &op);
|
|
1251
1322
|
}
|
|
1252
1323
|
|
|
1253
1324
|
// Free the option buffers that oj_parse_options() allocated for a single call.
|
|
@@ -1671,6 +1742,7 @@ static VALUE dump(int argc, VALUE *argv, VALUE self) {
|
|
|
1671
1742
|
struct dump_arg arg;
|
|
1672
1743
|
struct _out out;
|
|
1673
1744
|
struct _options copts = oj_default_options;
|
|
1745
|
+
VALUE to_json_argv[2];
|
|
1674
1746
|
|
|
1675
1747
|
if (1 > argc) {
|
|
1676
1748
|
rb_raise(rb_eArgError, "wrong number of arguments (0 for 1).");
|
|
@@ -1679,7 +1751,11 @@ static VALUE dump(int argc, VALUE *argv, VALUE self) {
|
|
|
1679
1751
|
copts.dump_opts.nan_dump = WordNan;
|
|
1680
1752
|
}
|
|
1681
1753
|
if (2 == argc) {
|
|
1682
|
-
|
|
1754
|
+
// The options hash is also what to_json and as_json receive, so
|
|
1755
|
+
// hand them the hash without the options Oj itself acted on.
|
|
1756
|
+
to_json_argv[0] = argv[0];
|
|
1757
|
+
to_json_argv[1] = oj_parse_options_rest(argv[1], &copts);
|
|
1758
|
+
argv = to_json_argv;
|
|
1683
1759
|
}
|
|
1684
1760
|
if (CompatMode == copts.mode && copts.escape_mode != ASCIIEsc) {
|
|
1685
1761
|
copts.escape_mode = JSONEsc;
|
data/ext/oj/oj.h
CHANGED
|
@@ -255,12 +255,14 @@ extern VALUE oj_compat_parse_cstr(int argc, VALUE *argv, char *json, size_t len)
|
|
|
255
255
|
extern VALUE oj_object_parse_cstr(int argc, VALUE *argv, char *json, size_t len);
|
|
256
256
|
extern VALUE oj_custom_parse_cstr(int argc, VALUE *argv, char *json, size_t len);
|
|
257
257
|
|
|
258
|
-
extern bool
|
|
259
|
-
extern void
|
|
260
|
-
extern
|
|
261
|
-
extern
|
|
262
|
-
extern void
|
|
263
|
-
extern void
|
|
258
|
+
extern bool oj_hash_has_key(VALUE hash, VALUE key);
|
|
259
|
+
extern void oj_parse_options(VALUE ropts, Options copts);
|
|
260
|
+
extern bool oj_parse_options_consumed(VALUE ropts, Options copts);
|
|
261
|
+
extern VALUE oj_parse_options_rest(VALUE ropts, Options copts);
|
|
262
|
+
extern void oj_free_call_options(Options copts);
|
|
263
|
+
extern void oj_options_take_ownership(Options copts);
|
|
264
|
+
extern void oj_options_release(Options copts);
|
|
265
|
+
extern void oj_options_mark(Options copts);
|
|
264
266
|
|
|
265
267
|
extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
|
|
266
268
|
extern void oj_dump_obj_to_json_using_params(VALUE obj, Options copts, Out out, int argc, VALUE *argv);
|
data/ext/oj/parse.c
CHANGED
|
@@ -1261,7 +1261,11 @@ oj_pi_parse(int argc, VALUE *argv, ParseInfo pi, char *json, size_t len, int yie
|
|
|
1261
1261
|
// value stack (while it is in scope).
|
|
1262
1262
|
wrapped_stack = oj_stack_init(&pi->stack);
|
|
1263
1263
|
rb_protect(protect_parse, (VALUE)pi, &line);
|
|
1264
|
-
|
|
1264
|
+
// Only when nothing is on the stack was the input actually empty. A
|
|
1265
|
+
// deferred hash (built at its closing brace from buffered pairs) leaves
|
|
1266
|
+
// head->val Qundef while its entry is still on the stack, e.g. when a
|
|
1267
|
+
// parse error cut the document short; the real error is already set.
|
|
1268
|
+
if (Qundef == pi->stack.head->val && stack_empty(&pi->stack) && !empty_ok(&pi->options)) {
|
|
1265
1269
|
if (No == pi->options.nilnil || (CompatMode == pi->options.mode && 0 < pi->cur - pi->json)) {
|
|
1266
1270
|
oj_set_error_at(pi, oj_json_parser_error_class, __FILE__, __LINE__, "Empty input");
|
|
1267
1271
|
}
|
data/ext/oj/parse.h
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#define OJ_PARSE_H
|
|
6
6
|
|
|
7
7
|
#include <stdarg.h>
|
|
8
|
+
#include <stddef.h>
|
|
8
9
|
#include <stdio.h>
|
|
9
10
|
#include <string.h>
|
|
10
11
|
|
|
@@ -90,7 +91,19 @@ extern VALUE oj_cstr_to_value(const char *str, size_t len, size_t cache_str);
|
|
|
90
91
|
extern VALUE oj_calc_hash_key(ParseInfo pi, Val parent);
|
|
91
92
|
|
|
92
93
|
static inline void parse_info_init(ParseInfo pi) {
|
|
93
|
-
|
|
94
|
+
// Zeroing the whole struct costs more than parsing a small document: the
|
|
95
|
+
// embedded reader buffer (rd.base, 4KB) and value stack entries
|
|
96
|
+
// (stack.base, 6.5KB) dominate its 11KB. Both are fully initialized
|
|
97
|
+
// before use — oj_reader_init() for stream parses, oj_stack_init() and
|
|
98
|
+
// stack_push() for the value stack — so zero only the scalars around
|
|
99
|
+
// them.
|
|
100
|
+
memset(pi, 0, offsetof(struct _parseInfo, rd));
|
|
101
|
+
memset((char *)pi + offsetof(struct _parseInfo, rd) + offsetof(struct _reader, head),
|
|
102
|
+
0,
|
|
103
|
+
offsetof(struct _parseInfo, stack) - offsetof(struct _parseInfo, rd) - offsetof(struct _reader, head));
|
|
104
|
+
memset((char *)pi + offsetof(struct _parseInfo, stack) + offsetof(struct _valStack, head),
|
|
105
|
+
0,
|
|
106
|
+
sizeof(struct _parseInfo) - offsetof(struct _parseInfo, stack) - offsetof(struct _valStack, head));
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
extern void oj_scanner_init(void);
|
data/ext/oj/parser.c
CHANGED
|
@@ -509,20 +509,29 @@ static void calc_num(ojParser p) {
|
|
|
509
509
|
d = -d;
|
|
510
510
|
}
|
|
511
511
|
if (0 < p->num.shift) {
|
|
512
|
-
|
|
513
|
-
}
|
|
514
|
-
if (0 < p->num.exp) {
|
|
515
|
-
long double x;
|
|
512
|
+
uint32_t shift = p->num.shift;
|
|
516
513
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
x = pow_map[p->num.exp];
|
|
514
|
+
while (MAX_POW < shift) {
|
|
515
|
+
d /= pow_map[MAX_POW];
|
|
516
|
+
shift -= MAX_POW;
|
|
521
517
|
}
|
|
518
|
+
d /= pow_map[shift];
|
|
519
|
+
}
|
|
520
|
+
if (0 < p->num.exp) {
|
|
522
521
|
if (p->num.exp_neg) {
|
|
523
|
-
|
|
522
|
+
int exp = p->num.exp;
|
|
523
|
+
|
|
524
|
+
while (MAX_POW < exp) {
|
|
525
|
+
d /= pow_map[MAX_POW];
|
|
526
|
+
exp -= MAX_POW;
|
|
527
|
+
}
|
|
528
|
+
d /= pow_map[exp];
|
|
524
529
|
} else {
|
|
525
|
-
|
|
530
|
+
if (MAX_POW < p->num.exp) {
|
|
531
|
+
d *= powl(10.0L, (long double)p->num.exp);
|
|
532
|
+
} else {
|
|
533
|
+
d *= pow_map[p->num.exp];
|
|
534
|
+
}
|
|
526
535
|
}
|
|
527
536
|
}
|
|
528
537
|
p->num.dub = d;
|
|
@@ -559,19 +568,31 @@ static void big_change(ojParser p) {
|
|
|
559
568
|
break;
|
|
560
569
|
case OJ_DECIMAL: {
|
|
561
570
|
int shift = p->num.shift;
|
|
571
|
+
int end = sizeof(buf) - 1;
|
|
562
572
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
buf[len] = '0' + (i % 10);
|
|
569
|
-
}
|
|
573
|
+
len = end;
|
|
574
|
+
do {
|
|
575
|
+
buf[--len] = '0' + (i % 10);
|
|
576
|
+
i /= 10;
|
|
577
|
+
} while (0 < i);
|
|
570
578
|
if (p->num.neg) {
|
|
571
|
-
buf
|
|
572
|
-
|
|
579
|
+
buf_append(&p->buf, '-');
|
|
580
|
+
}
|
|
581
|
+
if (end - len <= shift) {
|
|
582
|
+
buf_append_string(&p->buf, "0.", 2);
|
|
583
|
+
for (int cnt = shift - (end - len); 0 < cnt; cnt--) {
|
|
584
|
+
buf_append(&p->buf, '0');
|
|
585
|
+
}
|
|
586
|
+
buf_append_string(&p->buf, buf + len, end - len);
|
|
587
|
+
} else {
|
|
588
|
+
int decimal = end - shift;
|
|
589
|
+
|
|
590
|
+
buf_append_string(&p->buf, buf + len, decimal - len);
|
|
591
|
+
if (0 < shift) {
|
|
592
|
+
buf_append(&p->buf, '.');
|
|
593
|
+
buf_append_string(&p->buf, buf + decimal, shift);
|
|
594
|
+
}
|
|
573
595
|
}
|
|
574
|
-
buf_append_string(&p->buf, buf + len + 1, sizeof(buf) - len - 1);
|
|
575
596
|
if (0 < p->num.exp) {
|
|
576
597
|
int x = p->num.exp;
|
|
577
598
|
int d, div;
|
|
@@ -585,6 +606,7 @@ static void big_change(ojParser p) {
|
|
|
585
606
|
d = x / div % 10;
|
|
586
607
|
if (started || 0 < d) {
|
|
587
608
|
buf_append(&p->buf, '0' + d);
|
|
609
|
+
started = true;
|
|
588
610
|
}
|
|
589
611
|
}
|
|
590
612
|
}
|
data/ext/oj/parser.h
CHANGED
data/ext/oj/rails.c
CHANGED
|
@@ -765,11 +765,15 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
|
|
|
765
765
|
e->arg = rb_hash_new();
|
|
766
766
|
}
|
|
767
767
|
// An encoder given no options of its own reads oj_default_options at encode
|
|
768
|
-
// time rather than copying it here. ActiveSupport keeps
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
768
|
+
// time rather than copying it here. ActiveSupport keeps such an encoder for
|
|
769
|
+
// the life of the process, so a copy taken now would freeze every later
|
|
770
|
+
// Oj.default_options and ActiveSupport::JSON::Encoding change out of it -
|
|
771
|
+
// including the time_precision that set_encoder itself writes after
|
|
772
|
+
// ActiveSupport has already built and cached that encoder.
|
|
773
|
+
//
|
|
774
|
+
// A hash counts as options only if at least one key names an option Oj
|
|
775
|
+
// knows. Non-empty is not enough: ActiveSupport 8.1 caches a second encoder
|
|
776
|
+
// built with {escape: false}, a key Oj has never had.
|
|
773
777
|
e->own_opts = T_HASH == rb_type(e->arg) && 0 < RHASH_SIZE(e->arg);
|
|
774
778
|
if (e->own_opts) {
|
|
775
779
|
e->opts = oj_default_options;
|
|
@@ -778,9 +782,13 @@ static VALUE encoder_new(int argc, VALUE *argv, VALUE self) {
|
|
|
778
782
|
// its own that is freed with the encoder.
|
|
779
783
|
e->opts.str_rx.head = NULL;
|
|
780
784
|
e->opts.str_rx.tail = NULL;
|
|
781
|
-
|
|
785
|
+
e->own_opts = oj_parse_options_consumed(e->arg, &e->opts);
|
|
786
|
+
}
|
|
787
|
+
if (e->own_opts) {
|
|
782
788
|
oj_options_take_ownership(&e->opts);
|
|
783
789
|
} else {
|
|
790
|
+
// Nothing to free: every option that allocates is reached through a
|
|
791
|
+
// recognized key, and ownership is not taken until after that check.
|
|
784
792
|
memset(&e->opts, 0, sizeof(e->opts));
|
|
785
793
|
}
|
|
786
794
|
|
|
@@ -1176,8 +1184,6 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1176
1184
|
} else {
|
|
1177
1185
|
rb_raise(rb_eStandardError, "ActiveSupport not loaded.");
|
|
1178
1186
|
}
|
|
1179
|
-
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1180
|
-
|
|
1181
1187
|
json = rb_const_get_at(active, rb_intern("JSON"));
|
|
1182
1188
|
encoding = rb_const_get_at(json, rb_intern("Encoding"));
|
|
1183
1189
|
|
|
@@ -1204,6 +1210,12 @@ static VALUE rails_set_encoder(VALUE self) {
|
|
|
1204
1210
|
rb_define_module_function(encoding, "time_precision=", rails_time_precision, 1);
|
|
1205
1211
|
rb_gv_set("$VERBOSE", verbose);
|
|
1206
1212
|
|
|
1213
|
+
// Last, because ActiveSupport builds and caches encoders inside
|
|
1214
|
+
// json_encoder= and they should see the settings above already in the
|
|
1215
|
+
// defaults. escape_html and xml_time are read ahead of it for the same
|
|
1216
|
+
// reason.
|
|
1217
|
+
rb_funcall(active, rb_intern("json_encoder="), 1, encoder_class);
|
|
1218
|
+
|
|
1207
1219
|
return Qnil;
|
|
1208
1220
|
}
|
|
1209
1221
|
|
data/ext/oj/simd.h
CHANGED
|
@@ -99,9 +99,11 @@ static inline int oj_ctz64_fallback(uint64_t x) {
|
|
|
99
99
|
// GCC/Clang: check for header availability and include them
|
|
100
100
|
// We include headers but use target attributes to enable instructions per-function
|
|
101
101
|
// Include cpuid.h for __get_cpuid fallback when __builtin_cpu_supports is unavailable
|
|
102
|
+
#ifdef __has_include
|
|
102
103
|
#if __has_include(<cpuid.h>)
|
|
103
104
|
#include <cpuid.h>
|
|
104
105
|
#endif
|
|
106
|
+
#endif
|
|
105
107
|
#if defined(__SSE4_2__) || defined(__SSE2__)
|
|
106
108
|
// If any SSE is enabled globally, x86intrin.h should be available
|
|
107
109
|
#include <x86intrin.h>
|
|
@@ -109,6 +111,7 @@ static inline int oj_ctz64_fallback(uint64_t x) {
|
|
|
109
111
|
#define HAVE_SIMD_SSE2 1
|
|
110
112
|
#else
|
|
111
113
|
// Try to include headers anyway for target attribute functions
|
|
114
|
+
#ifdef __has_include
|
|
112
115
|
#if __has_include(<x86intrin.h>)
|
|
113
116
|
#include <x86intrin.h>
|
|
114
117
|
#define HAVE_SIMD_SSE4_2 1
|
|
@@ -123,6 +126,7 @@ static inline int oj_ctz64_fallback(uint64_t x) {
|
|
|
123
126
|
#endif
|
|
124
127
|
#endif
|
|
125
128
|
#endif
|
|
129
|
+
#endif
|
|
126
130
|
|
|
127
131
|
// Target attribute macros for function-level SIMD enabling
|
|
128
132
|
#if defined(__clang__) || defined(__GNUC__)
|
data/ext/oj/strict.c
CHANGED
|
@@ -47,7 +47,29 @@ VALUE oj_calc_hash_key(ParseInfo pi, Val parent) {
|
|
|
47
47
|
return rkey;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// With the default Hash class the pairs of the just closed hash sit on top
|
|
51
|
+
// of the pair buffer; build the Hash in one bulk insert with the final size
|
|
52
|
+
// known instead of one rb_hash_aset per pair (which converts the small
|
|
53
|
+
// ar_table to an st_table mid-insertion for hashes above 8 entries).
|
|
50
54
|
static void hash_end(ParseInfo pi) {
|
|
55
|
+
if (Qnil == pi->options.hash_class) {
|
|
56
|
+
ValStack stack = &pi->stack;
|
|
57
|
+
Val hash = stack_peek(stack);
|
|
58
|
+
size_t cnt = hash->pcnt;
|
|
59
|
+
volatile VALUE h;
|
|
60
|
+
|
|
61
|
+
#if HAVE_RB_HASH_NEW_CAPA
|
|
62
|
+
h = rb_hash_new_capa((long)(cnt / 2));
|
|
63
|
+
#else
|
|
64
|
+
h = rb_hash_new();
|
|
65
|
+
#endif
|
|
66
|
+
if (0 < cnt) {
|
|
67
|
+
rb_hash_bulk_insert((long)cnt, stack->pairs + stack->pcnt - cnt, h);
|
|
68
|
+
stack->pcnt -= cnt;
|
|
69
|
+
hash->pcnt = 0;
|
|
70
|
+
}
|
|
71
|
+
hash->val = h;
|
|
72
|
+
}
|
|
51
73
|
TRACE_PARSE_HASH_END(pi->options.trace, pi);
|
|
52
74
|
}
|
|
53
75
|
|
|
@@ -84,13 +106,19 @@ static VALUE start_hash(ParseInfo pi) {
|
|
|
84
106
|
return rb_class_new_instance(0, NULL, pi->options.hash_class);
|
|
85
107
|
}
|
|
86
108
|
TRACE_PARSE_IN(pi->options.trace, "start_hash", pi);
|
|
87
|
-
|
|
109
|
+
// The Hash is built at hash_end from the buffered pairs.
|
|
110
|
+
return Qundef;
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
static void hash_set_cstr(ParseInfo pi, Val parent, const char *str, size_t len, const char *orig) {
|
|
91
114
|
volatile VALUE rstr = oj_cstr_to_value(str, len, (size_t)pi->options.cache_str);
|
|
92
115
|
|
|
93
|
-
|
|
116
|
+
if (Qnil == pi->options.hash_class) {
|
|
117
|
+
stack_pair_push(&pi->stack, oj_calc_hash_key(pi, parent), rstr);
|
|
118
|
+
parent->pcnt += 2;
|
|
119
|
+
} else {
|
|
120
|
+
rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), rstr);
|
|
121
|
+
}
|
|
94
122
|
TRACE_PARSE_CALL(pi->options.trace, "set_string", pi, rstr);
|
|
95
123
|
}
|
|
96
124
|
|
|
@@ -101,12 +129,22 @@ static void hash_set_num(ParseInfo pi, Val parent, NumInfo ni) {
|
|
|
101
129
|
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a number or other value");
|
|
102
130
|
}
|
|
103
131
|
v = oj_num_as_value(ni);
|
|
104
|
-
|
|
132
|
+
if (Qnil == pi->options.hash_class) {
|
|
133
|
+
stack_pair_push(&pi->stack, oj_calc_hash_key(pi, parent), v);
|
|
134
|
+
parent->pcnt += 2;
|
|
135
|
+
} else {
|
|
136
|
+
rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), v);
|
|
137
|
+
}
|
|
105
138
|
TRACE_PARSE_CALL(pi->options.trace, "set_number", pi, v);
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
static void hash_set_value(ParseInfo pi, Val parent, VALUE value) {
|
|
109
|
-
|
|
142
|
+
if (Qnil == pi->options.hash_class) {
|
|
143
|
+
stack_pair_push(&pi->stack, oj_calc_hash_key(pi, parent), value);
|
|
144
|
+
parent->pcnt += 2;
|
|
145
|
+
} else {
|
|
146
|
+
rb_hash_aset(stack_peek(&pi->stack)->val, oj_calc_hash_key(pi, parent), value);
|
|
147
|
+
}
|
|
110
148
|
TRACE_PARSE_CALL(pi->options.trace, "set_value", pi, value);
|
|
111
149
|
}
|
|
112
150
|
|
data/ext/oj/usual.c
CHANGED
|
@@ -49,7 +49,12 @@ static VALUE form_str(const char *str, size_t len) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
static VALUE form_sym(const char *str, size_t len) {
|
|
52
|
-
|
|
52
|
+
VALUE sym = rb_check_symbol_cstr(str, (long)len, oj_utf8_encoding);
|
|
53
|
+
|
|
54
|
+
if (Qnil == sym) {
|
|
55
|
+
sym = rb_str_intern(rb_utf8_str_new(str, len));
|
|
56
|
+
}
|
|
57
|
+
return sym;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
static VALUE form_attr(const char *str, size_t len) {
|
|
@@ -172,9 +177,9 @@ static VALUE str_key(ojParser p, Key kp) {
|
|
|
172
177
|
|
|
173
178
|
static VALUE sym_key(ojParser p, Key kp) {
|
|
174
179
|
if ((size_t)kp->len < sizeof(kp->buf)) {
|
|
175
|
-
return
|
|
180
|
+
return form_sym(kp->buf, kp->len);
|
|
176
181
|
}
|
|
177
|
-
return
|
|
182
|
+
return form_sym(kp->key, kp->len);
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
static ID get_attr_id(ojParser p, Key kp) {
|
data/ext/oj/val_stack.c
CHANGED
|
@@ -21,6 +21,13 @@ static void stack_mark(void *ptr) {
|
|
|
21
21
|
rb_mutex_lock(stack->mutex);
|
|
22
22
|
rb_gc_mark(stack->mutex);
|
|
23
23
|
#endif
|
|
24
|
+
{
|
|
25
|
+
size_t i;
|
|
26
|
+
|
|
27
|
+
for (i = 0; i < stack->pcnt; i++) {
|
|
28
|
+
rb_gc_mark(stack->pairs[i]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
24
31
|
for (v = stack->head; v < stack->tail; v++) {
|
|
25
32
|
if (Qnil != v->val && Qundef != v->val) {
|
|
26
33
|
rb_gc_mark(v->val);
|
|
@@ -71,6 +78,9 @@ oj_stack_init(ValStack stack) {
|
|
|
71
78
|
stack->head = stack->base;
|
|
72
79
|
stack->end = stack->base + sizeof(stack->base) / sizeof(struct _val);
|
|
73
80
|
stack->tail = stack->head;
|
|
81
|
+
stack->pairs = stack->pbase;
|
|
82
|
+
stack->pcnt = 0;
|
|
83
|
+
stack->pend = sizeof(stack->pbase) / sizeof(VALUE);
|
|
74
84
|
stack->head->val = Qundef;
|
|
75
85
|
stack->head->key = NULL;
|
|
76
86
|
stack->head->key_val = Qundef;
|
|
@@ -79,6 +89,7 @@ oj_stack_init(ValStack stack) {
|
|
|
79
89
|
stack->head->clas = Qundef;
|
|
80
90
|
stack->head->klen = 0;
|
|
81
91
|
stack->head->clen = 0;
|
|
92
|
+
stack->head->pcnt = 0;
|
|
82
93
|
stack->head->next = NEXT_NONE;
|
|
83
94
|
|
|
84
95
|
return TypedData_Wrap_Struct(oj_cstack_class, &oj_stack_type, stack);
|
data/ext/oj/val_stack.h
CHANGED
|
@@ -37,16 +37,27 @@ typedef struct _val {
|
|
|
37
37
|
OddArgs odd_args;
|
|
38
38
|
size_t klen;
|
|
39
39
|
size_t clen;
|
|
40
|
+
size_t pcnt; // VALUEs this open hash holds in the stack pair buffer
|
|
40
41
|
char next; // ValNext
|
|
41
42
|
char k1; // first original character in the key
|
|
42
43
|
char kalloc;
|
|
43
44
|
} *Val;
|
|
44
45
|
|
|
46
|
+
#define PAIR_BASE_CNT 64
|
|
47
|
+
|
|
45
48
|
typedef struct _valStack {
|
|
46
49
|
struct _val base[STACK_INC];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
VALUE pbase[PAIR_BASE_CNT]; // initial buffer for pairs
|
|
51
|
+
// Flat key/value pair buffer for hash modes that build each Hash in one
|
|
52
|
+
// rb_hash_bulk_insert at the closing brace instead of one rb_hash_aset
|
|
53
|
+
// per pair. Nesting is strictly LIFO so each open hash's pairs occupy
|
|
54
|
+
// the top of the buffer; the per-hash count lives in its Val's clen.
|
|
55
|
+
VALUE *pairs;
|
|
56
|
+
size_t pcnt; // number of live VALUEs in pairs
|
|
57
|
+
size_t pend; // capacity of pairs in VALUEs
|
|
58
|
+
Val head; // current stack
|
|
59
|
+
Val end; // stack end
|
|
60
|
+
Val tail; // pointer to one past last element name on stack
|
|
50
61
|
#ifdef HAVE_PTHREAD_MUTEX_INIT
|
|
51
62
|
pthread_mutex_t mutex;
|
|
52
63
|
#else
|
|
@@ -76,6 +87,11 @@ inline static void stack_cleanup(ValStack stack) {
|
|
|
76
87
|
OJ_R_FREE(stack->head);
|
|
77
88
|
stack->head = NULL;
|
|
78
89
|
}
|
|
90
|
+
if (stack->pbase != stack->pairs) {
|
|
91
|
+
OJ_R_FREE(stack->pairs);
|
|
92
|
+
stack->pairs = NULL;
|
|
93
|
+
}
|
|
94
|
+
stack->pcnt = 0;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
inline static void stack_push(ValStack stack, VALUE val, ValNext next) {
|
|
@@ -124,6 +140,7 @@ inline static void stack_push(ValStack stack, VALUE val, ValNext next) {
|
|
|
124
140
|
stack->tail->key_val = Qundef;
|
|
125
141
|
stack->tail->clen = 0;
|
|
126
142
|
stack->tail->klen = 0;
|
|
143
|
+
stack->tail->pcnt = 0;
|
|
127
144
|
stack->tail->kalloc = 0;
|
|
128
145
|
stack->tail++;
|
|
129
146
|
}
|
|
@@ -165,6 +182,40 @@ inline static Val stack_pop(ValStack stack) {
|
|
|
165
182
|
return 0;
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
// Append one key/value pair to the pair buffer. The same GC discipline as
|
|
186
|
+
// stack_push: the allocation happens outside the mutex (it can trigger a GC
|
|
187
|
+
// which takes the mutex in stack_mark) and the buffer pointer only changes
|
|
188
|
+
// under the mutex.
|
|
189
|
+
inline static void stack_pair_push(ValStack stack, VALUE key, VALUE value) {
|
|
190
|
+
if (stack->pend <= stack->pcnt + 2) {
|
|
191
|
+
size_t cnt = stack->pend * 2;
|
|
192
|
+
VALUE *pairs;
|
|
193
|
+
|
|
194
|
+
if (stack->pbase == stack->pairs) {
|
|
195
|
+
pairs = OJ_R_ALLOC_N(VALUE, cnt);
|
|
196
|
+
memcpy(pairs, stack->pairs, sizeof(VALUE) * stack->pcnt);
|
|
197
|
+
} else {
|
|
198
|
+
pairs = stack->pairs;
|
|
199
|
+
OJ_R_REALLOC_N(pairs, VALUE, cnt);
|
|
200
|
+
}
|
|
201
|
+
#ifdef HAVE_PTHREAD_MUTEX_INIT
|
|
202
|
+
pthread_mutex_lock(&stack->mutex);
|
|
203
|
+
#else
|
|
204
|
+
rb_mutex_lock(stack->mutex);
|
|
205
|
+
#endif
|
|
206
|
+
stack->pairs = pairs;
|
|
207
|
+
stack->pend = cnt;
|
|
208
|
+
#ifdef HAVE_PTHREAD_MUTEX_INIT
|
|
209
|
+
pthread_mutex_unlock(&stack->mutex);
|
|
210
|
+
#else
|
|
211
|
+
rb_mutex_unlock(stack->mutex);
|
|
212
|
+
#endif
|
|
213
|
+
}
|
|
214
|
+
stack->pairs[stack->pcnt] = key;
|
|
215
|
+
stack->pairs[stack->pcnt + 1] = value;
|
|
216
|
+
stack->pcnt += 2;
|
|
217
|
+
}
|
|
218
|
+
|
|
168
219
|
extern const char *oj_stack_next_string(ValNext n);
|
|
169
220
|
|
|
170
221
|
#endif /* OJ_VAL_STACK_H */
|
data/lib/oj/version.rb
CHANGED
data/pages/Rails.md
CHANGED
|
@@ -91,6 +91,10 @@ The classes that can be put in optimized mode and are optimized when
|
|
|
91
91
|
|
|
92
92
|
Both `Oj::Rails` and each `Oj::Rails::Encoder` have `optimize()`, `deoptimize()`, and `optimized?()` methods. The module level methods change the setting for the whole process. An encoder follows those process wide settings until `optimize()` or `deoptimize()` is called on the encoder itself. From then on the encoder keeps a set of its own and later module level changes do not reach it.
|
|
93
93
|
|
|
94
|
+
Options work the same way. An encoder reads `Oj.default_options` when it encodes, so later changes to the defaults reach it, unless it was given options of its own, in which case it keeps the copy it took when it was created. An encoder counts as having options of its own only if the hash it was built with names at least one option Oj knows. A hash of nothing but keys Oj does not have, such as the `escape: false` ActiveSupport 8.1 builds one of its cached encoders with, leaves the encoder on the defaults.
|
|
95
|
+
|
|
96
|
+
Once a class is optimized its `as_json()` method is no longer called, so an application that defines its own `as_json()` on an optimized class, `BigDecimal` for example, will not see it used. Call `Oj::Rails.deoptimize(BigDecimal)` after `Oj.optimize_rails` to put that one class back on the `as_json()` path and leave the rest optimized.
|
|
97
|
+
|
|
94
98
|
The ActiveSupport decoder is the `JSON.parse()` method. Calling the
|
|
95
99
|
`Oj::Rails.set_decoder()` method replaces that method with the Oj equivalent.
|
|
96
100
|
|