oj 3.17.6 → 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 +13 -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 +56 -12
- data/ext/oj/oj.h +8 -7
- 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/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
- 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,18 @@
|
|
|
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
|
+
|
|
3
16
|
## 3.17.6 - 2026-08-10
|
|
4
17
|
|
|
5
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)
|
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
|
@@ -1241,45 +1241,84 @@ static bool parse_one_option(VALUE k, VALUE v, Options copts) {
|
|
|
1241
1241
|
return true;
|
|
1242
1242
|
}
|
|
1243
1243
|
|
|
1244
|
-
// Collects the
|
|
1244
|
+
// Collects the results of parse_one_option() over every key in a hash.
|
|
1245
1245
|
struct _optsParse {
|
|
1246
1246
|
Options copts;
|
|
1247
|
-
bool consumed;
|
|
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
|
|
1248
1250
|
};
|
|
1249
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
|
+
|
|
1250
1259
|
static int parse_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
1251
1260
|
struct _optsParse *op = (struct _optsParse *)opts;
|
|
1252
1261
|
|
|
1253
1262
|
if (parse_one_option(k, v, op->copts)) {
|
|
1254
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
|
+
}
|
|
1255
1269
|
}
|
|
1256
1270
|
return ST_CONTINUE;
|
|
1257
1271
|
}
|
|
1258
1272
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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;
|
|
1264
1278
|
|
|
1265
1279
|
if (T_HASH != rb_type(ropts)) {
|
|
1266
|
-
return
|
|
1280
|
+
return;
|
|
1267
1281
|
}
|
|
1268
|
-
rb_hash_foreach(ropts, parse_options_cb, (VALUE)
|
|
1282
|
+
rb_hash_foreach(ropts, parse_options_cb, (VALUE)op);
|
|
1269
1283
|
if (Qnil != rb_hash_lookup(ropts, match_string_sym)) {
|
|
1270
|
-
op
|
|
1284
|
+
op->consumed = true;
|
|
1285
|
+
drop_from_rest(op, match_string_sym);
|
|
1271
1286
|
}
|
|
1272
1287
|
oj_parse_opt_match_string(&copts->str_rx, ropts);
|
|
1273
1288
|
|
|
1274
1289
|
copts->dump_opts.use = (0 < copts->dump_opts.indent_size || 0 < copts->dump_opts.after_size ||
|
|
1275
1290
|
0 < copts->dump_opts.before_size || 0 < copts->dump_opts.hash_size ||
|
|
1276
1291
|
0 < copts->dump_opts.array_size);
|
|
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);
|
|
1277
1301
|
|
|
1278
1302
|
return op.consumed;
|
|
1279
1303
|
}
|
|
1280
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
|
+
|
|
1281
1318
|
void oj_parse_options(VALUE ropts, Options copts) {
|
|
1282
|
-
|
|
1319
|
+
struct _optsParse op;
|
|
1320
|
+
|
|
1321
|
+
parse_options(ropts, copts, &op);
|
|
1283
1322
|
}
|
|
1284
1323
|
|
|
1285
1324
|
// Free the option buffers that oj_parse_options() allocated for a single call.
|
|
@@ -1703,6 +1742,7 @@ static VALUE dump(int argc, VALUE *argv, VALUE self) {
|
|
|
1703
1742
|
struct dump_arg arg;
|
|
1704
1743
|
struct _out out;
|
|
1705
1744
|
struct _options copts = oj_default_options;
|
|
1745
|
+
VALUE to_json_argv[2];
|
|
1706
1746
|
|
|
1707
1747
|
if (1 > argc) {
|
|
1708
1748
|
rb_raise(rb_eArgError, "wrong number of arguments (0 for 1).");
|
|
@@ -1711,7 +1751,11 @@ static VALUE dump(int argc, VALUE *argv, VALUE self) {
|
|
|
1711
1751
|
copts.dump_opts.nan_dump = WordNan;
|
|
1712
1752
|
}
|
|
1713
1753
|
if (2 == argc) {
|
|
1714
|
-
|
|
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;
|
|
1715
1759
|
}
|
|
1716
1760
|
if (CompatMode == copts.mode && copts.escape_mode != ASCIIEsc) {
|
|
1717
1761
|
copts.escape_mode = JSONEsc;
|
data/ext/oj/oj.h
CHANGED
|
@@ -255,13 +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 bool
|
|
261
|
-
extern
|
|
262
|
-
extern void
|
|
263
|
-
extern void
|
|
264
|
-
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);
|
|
265
266
|
|
|
266
267
|
extern void oj_dump_obj_to_json(VALUE obj, Options copts, Out out);
|
|
267
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/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