oj 3.17.4 → 3.17.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +69 -0
- data/ext/oj/cache.c +17 -1
- data/ext/oj/cache.h +5 -0
- data/ext/oj/circarray.c +6 -2
- data/ext/oj/compat.c +2 -2
- data/ext/oj/custom.c +5 -3
- data/ext/oj/dump.c +27 -12
- data/ext/oj/dump.h +10 -0
- data/ext/oj/dump_compat.c +10 -4
- data/ext/oj/extconf.rb +1 -0
- data/ext/oj/fast.c +40 -5
- data/ext/oj/intern.c +6 -1
- data/ext/oj/object.c +15 -10
- data/ext/oj/odd.c +6 -2
- data/ext/oj/oj.c +53 -6
- data/ext/oj/oj.h +1 -1
- data/ext/oj/parse.c +8 -1
- data/ext/oj/parser.c +57 -25
- data/ext/oj/rails.c +162 -52
- data/ext/oj/reader.c +2 -2
- data/ext/oj/resolve.c +11 -2
- data/ext/oj/safe.c +62 -48
- data/ext/oj/safe.h +14 -2
- data/ext/oj/saj.c +6 -0
- data/ext/oj/sparse.c +7 -6
- data/ext/oj/string_writer.c +2 -0
- data/ext/oj/usual.c +5 -1
- data/ext/oj/util.c +27 -0
- data/ext/oj/util.h +2 -1
- data/ext/oj/val_stack.h +24 -5
- data/ext/oj/wab.c +14 -26
- data/lib/oj/version.rb +1 -1
- data/pages/Compatibility.md +1 -1
- data/pages/Custom.md +2 -3
- data/pages/InstallOptions.md +8 -6
- data/pages/Modes.md +1 -1
- data/pages/Options.md +5 -6
- data/pages/Parser.md +2 -2
- data/pages/Rails.md +2 -0
- data/pages/Security.md +21 -7
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 62e3d04685cbc7e063801fd45fc2e9c86a3695ac1fff9235d49944fb19a1ba50
|
|
4
|
+
data.tar.gz: 98cd36664b62b1d774998a5c39d4ea1610fbeb5a4d789de2bd80894bf9121fbe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '029e6458460f15ae5bcbf26bb25ff959b5c3a0b70cf04b5eded0b2ff50930bbd3470b51410590fbf6621640590257360e0d20b290af8c28efbb31ee0a602998b'
|
|
7
|
+
data.tar.gz: c2442be3533074487f3fa7b7e84fac719748cd3f99c40217dc6f6dbc09600e491b2925905f400e2ac7de5639d78d15f76f01f5865a41dda2f963e6ccfd59223c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,74 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 3.17.5 - 2026-07-31
|
|
4
|
+
|
|
5
|
+
Most of this release comes out of a security review of the C extension. There are no API changes.
|
|
6
|
+
|
|
7
|
+
### Memory safety
|
|
8
|
+
|
|
9
|
+
- Fixed hash flooding by seeding the key hash per process. The hash was decided entirely at compile time, so a document of colliding keys turned key interning into O(n^2) and left the process slow for every later parse. (#1053)
|
|
10
|
+
- Fixed a one byte buffer over-read on a document that ends with a backslash inside an unterminated string. (#1054)
|
|
11
|
+
- Fixed two length underflows that made an empty `^m` value and a key of exactly 65,536 bytes read forward until they left mapped memory. (#1055)
|
|
12
|
+
- Fixed a SEGV from type confusion in class path resolution. A name segment that resolved to something other than a class or module was dereferenced as one. (#1056)
|
|
13
|
+
- Fixed uninitialized stack memory being read into the message of an unresolved class name error. (#1057)
|
|
14
|
+
- Fixed output buffer overflows when writing indentation, where several callers of `fill_indent()` had not assured the buffer first. (#1058)
|
|
15
|
+
- Fixed the `Oj::Doc` path depth limit being checked one level too late. (#1061)
|
|
16
|
+
- Fixed the use-after-free of a key when the value stack grows. (#1062)
|
|
17
|
+
- Fixed the buffer over-read when a document ends where a key belongs. (#1063)
|
|
18
|
+
- Fixed the id from a circular reference being used unchecked. (#1064)
|
|
19
|
+
- Fixed the SEGV dumping an exception that was never raised, where the missing backtrace was read as an Array. (#1068)
|
|
20
|
+
- Fixed the stack buffer overflow writing the error location. (#1071)
|
|
21
|
+
- Fixed a read error in `Oj::Parser#file` being taken as a read of `SIZE_MAX` bytes, which wrote in front of the buffer and then reparsed it forever without checking for interrupts. (#1072)
|
|
22
|
+
- Fixed a `:float_format` too long for the 64 byte buffer copying stack bytes past the end of it into the output. (#1073)
|
|
23
|
+
- Fixed the double free of the default `create_id` when a call passes `:create_id => nil`. (#1074)
|
|
24
|
+
- Fixed the buffer over-read of an odd attribute name and the leak of its arguments. (#1075)
|
|
25
|
+
- Fixed the SEGV reading a truncated document with `Oj::Parser#file` and the descriptor it leaked. (#1076)
|
|
26
|
+
- Fixed the SEGV from a malformed number in `Oj::Doc`. (#1077)
|
|
27
|
+
- Fixed the stack exhaustion from a long key with `:only` or `:except`. (#1078)
|
|
28
|
+
- Fixed the odd module match being decided by uninitialized memory. (#1079)
|
|
29
|
+
- Fixed `:float_format` accepting directives it has no argument for. (#1083)
|
|
30
|
+
|
|
31
|
+
### Memory leaks
|
|
32
|
+
|
|
33
|
+
- Fixed the leak of an escaped hash key in the streaming parser. (#1067)
|
|
34
|
+
- Fixed the leak of the parser `create_id` when it is set a second time. (#1085)
|
|
35
|
+
- Fixed the leak of the parser when `Oj::Parser.new` or `Oj::Parser.safe` raises on an argument it does not take. (#1086)
|
|
36
|
+
|
|
37
|
+
### Parsing and dumping
|
|
38
|
+
|
|
39
|
+
- Fixed a safe parser limit of 4 being ignored. (#1065)
|
|
40
|
+
- Fixed the exponent bound being checked after it is narrowed. (#1066)
|
|
41
|
+
- Fixed a document being rejected when it opens with a number of 4093 digits or more read through a file descriptor or an IO, and the reader buffer that leaked when it grew. (#1069)
|
|
42
|
+
- Fixed the safe parser limits being dropped when an option is set after the parser is built. (#1070)
|
|
43
|
+
- Fixed the `:custom` mode dump of an exception that was never raised opening with `{,`. (#1082)
|
|
44
|
+
- Fixed the `:wab` mode time load being an offset out on Windows, where `mktime()` stood in for `timegm()` and returned -1 for any time before the epoch. (#1088)
|
|
45
|
+
- Fixed `Oj::Doc` dropping the exponent of a number written without a decimal point, so `1e3` loaded as `1`. (#1089)
|
|
46
|
+
|
|
47
|
+
### Rails
|
|
48
|
+
|
|
49
|
+
- Fixed a `to_json(only:)` or `to_json(except:)` dropping the `include_root_in_json` wrapper key and the keys added by `:methods`. (#1039)
|
|
50
|
+
- Fixed the `as_json` options reaching only the first element of a container, so `to_json(only:)`, `to_json(methods:)` and any custom `as_json(opts)` took effect on one row and nowhere else. (#1040)
|
|
51
|
+
- An encoder now reads the global optimized class table and options instead of copying them, saving about 6.7KB of malloc per `ActiveSupport::JSON.encode`, and picks up later `Oj.default_options` and `ActiveSupport::JSON::Encoding` changes. (#1049)
|
|
52
|
+
- Fixed `as_json` being handed the encoder's own options hash. ActiveSupport allows `as_json` to write into it, and ActiveSupport 8.1 caches one encoder per process, so a single such `as_json` could leave `:only` set for every later `to_json`. (#1050)
|
|
53
|
+
|
|
54
|
+
### Documentation
|
|
55
|
+
|
|
56
|
+
- Renamed the documented `:skip_null_byte` option to `:omit_null_byte`, which is the name that works. (#1041)
|
|
57
|
+
- Dropped the `--with-sse42` install option from InstallOptions.md. SIMD support is detected at runtime. (#1042)
|
|
58
|
+
- Corrected the documented minimum Ruby version from 2.4+ to 2.7+. (#1043)
|
|
59
|
+
- Corrected the Parser.md benchmark conclusions to reference `:usual` rather than `:saj`. (#1044)
|
|
60
|
+
- Corrected Custom.md and Options.md, which called `:custom` the default mode. The default is `:object`. (#1045)
|
|
61
|
+
- Corrected the documented type of the `:safe` option from String to Boolean. (#1046)
|
|
62
|
+
- Corrected Security.md, which said no methods are ever called on an Object created by a load. `exception`, `set_backtrace` and `replace` are. The page now also says which mode `Oj.load` uses. (#1080)
|
|
63
|
+
|
|
64
|
+
### Tests and CI
|
|
65
|
+
|
|
66
|
+
- Added the ActiveSupport 8 test suite. The Rakefile picked the suite by major version, so the rails_8 job had been running zero ActiveSupport tests. (#1047)
|
|
67
|
+
- Added the ActiveSupport 8.1 test suite and pinned the #985 regression. (#1051)
|
|
68
|
+
- Fixed `rake` never running the `test/test_*.rb` suite. The `Rake::TestTask` that defined `test` was commented out, so `test_all` invoked a synthesized no-op file task. (#1084)
|
|
69
|
+
- Added a job timeout to the workflows. (#1087)
|
|
70
|
+
- Removed the pre-1970 time test skips on Windows, which were left from a 32 bit Ruby limit that no longer applies. (#1090)
|
|
71
|
+
|
|
3
72
|
## 3.17.4 - 2026-07-14
|
|
4
73
|
|
|
5
74
|
- Fixed issue in fast.c where rescuing from a panic did not reset the where counter.
|
data/ext/oj/cache.c
CHANGED
|
@@ -28,6 +28,22 @@
|
|
|
28
28
|
// almost the Murmur hash algorithm
|
|
29
29
|
#define M 0x5bd1e995
|
|
30
30
|
|
|
31
|
+
uint64_t oj_hash_seed = 0;
|
|
32
|
+
|
|
33
|
+
void oj_hash_seed_init(void) {
|
|
34
|
+
static bool seeded = false;
|
|
35
|
+
|
|
36
|
+
if (seeded) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
seeded = true;
|
|
40
|
+
#if HAVE_RB_HASH_START
|
|
41
|
+
oj_hash_seed = (uint64_t)rb_hash_start(0);
|
|
42
|
+
#else
|
|
43
|
+
oj_hash_seed = (uint64_t)NUM2LL(rb_funcall(rb_str_new_cstr("oj"), rb_intern("hash"), 0));
|
|
44
|
+
#endif
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
typedef struct _slot {
|
|
32
48
|
struct _slot *next;
|
|
33
49
|
VALUE val;
|
|
@@ -62,7 +78,7 @@ void cache_set_form(Cache c, VALUE (*form)(const char *str, size_t len)) {
|
|
|
62
78
|
static uint64_t hash_calc(const uint8_t *key, size_t len) {
|
|
63
79
|
const uint8_t *end = key + len;
|
|
64
80
|
const uint8_t *endless = key + (len & 0xFFFFFFFC);
|
|
65
|
-
uint64_t h = (uint64_t)len;
|
|
81
|
+
uint64_t h = (uint64_t)len ^ oj_hash_seed;
|
|
66
82
|
uint64_t k;
|
|
67
83
|
|
|
68
84
|
while (key < endless) {
|
data/ext/oj/cache.h
CHANGED
data/ext/oj/circarray.c
CHANGED
|
@@ -25,8 +25,12 @@ void oj_circ_array_free(CircArray ca) {
|
|
|
25
25
|
OJ_R_FREE(ca);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Objects are numbered in encounter order starting at 1, so the only id that
|
|
29
|
+
// can extend the array is one past the highest already seen. Anything further
|
|
30
|
+
// is not a reference to an object the document can contain and must not be
|
|
31
|
+
// used to size an allocation.
|
|
28
32
|
void oj_circ_array_set(CircArray ca, VALUE obj, unsigned long id) {
|
|
29
|
-
if (0 < id &&
|
|
33
|
+
if (0 != ca && 0 < id && id <= ca->cnt + 1) {
|
|
30
34
|
unsigned long i;
|
|
31
35
|
|
|
32
36
|
if (ca->size < id) {
|
|
@@ -57,7 +61,7 @@ VALUE
|
|
|
57
61
|
oj_circ_array_get(CircArray ca, unsigned long id) {
|
|
58
62
|
VALUE obj = Qnil;
|
|
59
63
|
|
|
60
|
-
if (
|
|
64
|
+
if (0 != ca && 0 < id && id <= ca->cnt) {
|
|
61
65
|
obj = ca->objs[id - 1];
|
|
62
66
|
}
|
|
63
67
|
return obj;
|
data/ext/oj/compat.c
CHANGED
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) {
|
|
16
16
|
const char *key = kval->key;
|
|
17
|
-
|
|
17
|
+
size_t klen = kval->klen;
|
|
18
18
|
Val parent = stack_peek(&pi->stack);
|
|
19
19
|
|
|
20
20
|
if (Qundef == kval->key_val && Yes == pi->options.create_ok && NULL != pi->options.create_id &&
|
|
21
|
-
*pi->options.create_id == *key && (
|
|
21
|
+
*pi->options.create_id == *key && (size_t)pi->options.create_id_len == klen &&
|
|
22
22
|
0 == strncmp(pi->options.create_id, key, klen)) {
|
|
23
23
|
parent->classname = oj_strndup(str, len);
|
|
24
24
|
parent->clen = len;
|
data/ext/oj/custom.c
CHANGED
|
@@ -640,7 +640,9 @@ static void dump_obj_attrs(VALUE obj, VALUE clas, slot_t id, int depth, Out out)
|
|
|
640
640
|
if (rb_obj_is_kind_of(obj, rb_eException)) {
|
|
641
641
|
volatile VALUE rv;
|
|
642
642
|
|
|
643
|
-
|
|
643
|
+
// An exception that was never raised has no instance variables, so
|
|
644
|
+
// without the class name nothing has been written since the '{'.
|
|
645
|
+
if (',' != *(out->cur - 1) && '{' != *(out->cur - 1)) {
|
|
644
646
|
*out->cur++ = ',';
|
|
645
647
|
}
|
|
646
648
|
// message
|
|
@@ -896,11 +898,11 @@ void oj_dump_custom_val(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
896
898
|
|
|
897
899
|
static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) {
|
|
898
900
|
const char *key = kval->key;
|
|
899
|
-
|
|
901
|
+
size_t klen = kval->klen;
|
|
900
902
|
Val parent = stack_peek(&pi->stack);
|
|
901
903
|
|
|
902
904
|
if (Qundef == kval->key_val && Yes == pi->options.create_ok && NULL != pi->options.create_id &&
|
|
903
|
-
*pi->options.create_id == *key && (
|
|
905
|
+
*pi->options.create_id == *key && (size_t)pi->options.create_id_len == klen &&
|
|
904
906
|
0 == strncmp(pi->options.create_id, key, klen)) {
|
|
905
907
|
parent->clas = oj_name2class(pi, str, len, false, rb_eArgError);
|
|
906
908
|
if (2 == klen && '^' == *key && 'o' == key[1]) {
|
data/ext/oj/dump.c
CHANGED
|
@@ -1456,10 +1456,11 @@ void oj_dump_raw(const char *str, size_t cnt, Out out) {
|
|
|
1456
1456
|
}
|
|
1457
1457
|
|
|
1458
1458
|
void oj_out_init(Out out) {
|
|
1459
|
-
out->buf
|
|
1460
|
-
out->cur
|
|
1461
|
-
out->end
|
|
1462
|
-
out->allocated
|
|
1459
|
+
out->buf = out->stack_buffer;
|
|
1460
|
+
out->cur = out->buf;
|
|
1461
|
+
out->end = out->buf + sizeof(out->stack_buffer) - BUFFER_EXTRA;
|
|
1462
|
+
out->allocated = false;
|
|
1463
|
+
out->key_filter_off = false;
|
|
1463
1464
|
}
|
|
1464
1465
|
|
|
1465
1466
|
void oj_out_free(Out out) {
|
|
@@ -1731,6 +1732,11 @@ void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) {
|
|
|
1731
1732
|
size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format) {
|
|
1732
1733
|
size_t cnt = snprintf(buf, blen, format, d);
|
|
1733
1734
|
|
|
1735
|
+
// snprintf() returns the length the output would have needed, which is
|
|
1736
|
+
// more than it wrote when the format asks for more room than buf has.
|
|
1737
|
+
if (blen <= cnt) {
|
|
1738
|
+
cnt = blen - 1;
|
|
1739
|
+
}
|
|
1734
1740
|
// Round off issues at 16 significant digits so check for obvious ones of
|
|
1735
1741
|
// 0001 and 9999.
|
|
1736
1742
|
if (17 <= cnt && (0 == strcmp("0001", buf + cnt - 4) || 0 == strcmp("9999", buf + cnt - 4))) {
|
|
@@ -1742,6 +1748,21 @@ size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const c
|
|
|
1742
1748
|
return cnt;
|
|
1743
1749
|
}
|
|
1744
1750
|
|
|
1751
|
+
// The only and except lists are the keys joined with a ':' and wrapped in one
|
|
1752
|
+
// at each end, so a key is in a list when ":key:" is in it. Matching in place
|
|
1753
|
+
// finds what searching the list for a copy of the key with the colons on it
|
|
1754
|
+
// found, without a buffer sized by the key.
|
|
1755
|
+
static bool key_listed(const char *list, const char *key, size_t klen) {
|
|
1756
|
+
const char *s;
|
|
1757
|
+
|
|
1758
|
+
for (s = list; NULL != (s = strchr(s, ':')); s++) {
|
|
1759
|
+
if (0 == strncmp(s + 1, key, klen) && ':' == s[klen + 1]) {
|
|
1760
|
+
return true;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1745
1766
|
bool oj_key_skip(VALUE key, const char *only, const char *except) {
|
|
1746
1767
|
const char *skey;
|
|
1747
1768
|
|
|
@@ -1751,15 +1772,9 @@ bool oj_key_skip(VALUE key, const char *only, const char *except) {
|
|
|
1751
1772
|
default: skey = NULL; break;
|
|
1752
1773
|
}
|
|
1753
1774
|
if (NULL != skey && '\0' != *skey) {
|
|
1754
|
-
size_t
|
|
1755
|
-
char *buf = alloca(size + 3);
|
|
1756
|
-
|
|
1757
|
-
buf[0] = ':';
|
|
1758
|
-
strcpy(buf + 1, skey);
|
|
1759
|
-
buf[size + 1] = ':';
|
|
1760
|
-
buf[size + 2] = '\0';
|
|
1775
|
+
size_t klen = strlen(skey);
|
|
1761
1776
|
|
|
1762
|
-
return ((NULL != only &&
|
|
1777
|
+
return ((NULL != only && !key_listed(only, skey, klen)) || (NULL != except && key_listed(except, skey, klen)));
|
|
1763
1778
|
}
|
|
1764
1779
|
return NULL != only;
|
|
1765
1780
|
}
|
data/ext/oj/dump.h
CHANGED
|
@@ -70,9 +70,19 @@ inline static void assure_size(Out out, size_t len) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// Bytes that fill_indent(), or the dump_opts equivalent used when :indent is a
|
|
74
|
+
// String, writes at this depth. nl_size is array_size or hash_size.
|
|
75
|
+
inline static size_t indent_len(Out out, int depth, size_t nl_size) {
|
|
76
|
+
if (out->opts->dump_opts.use) {
|
|
77
|
+
return depth * out->opts->dump_opts.indent_size + nl_size;
|
|
78
|
+
}
|
|
79
|
+
return depth * out->indent + 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
inline static void fill_indent(Out out, int cnt) {
|
|
74
83
|
if (0 < out->indent) {
|
|
75
84
|
cnt *= out->indent;
|
|
85
|
+
assure_size(out, cnt + 1);
|
|
76
86
|
*out->cur++ = '\n';
|
|
77
87
|
memset(out->cur, ' ', cnt);
|
|
78
88
|
out->cur += cnt;
|
data/ext/oj/dump_compat.c
CHANGED
|
@@ -299,9 +299,10 @@ static ID message_id = 0;
|
|
|
299
299
|
static ID backtrace_id = 0;
|
|
300
300
|
|
|
301
301
|
static void exception_alt(VALUE obj, int depth, Out out) {
|
|
302
|
-
int
|
|
303
|
-
size_t
|
|
304
|
-
size_t
|
|
302
|
+
int d3 = depth + 2;
|
|
303
|
+
size_t size = d3 * out->indent + 2;
|
|
304
|
+
size_t sep_len = out->opts->dump_opts.before_size + out->opts->dump_opts.after_size + 2;
|
|
305
|
+
volatile VALUE bt;
|
|
305
306
|
|
|
306
307
|
if (0 == message_id) {
|
|
307
308
|
message_id = rb_intern("message");
|
|
@@ -332,7 +333,12 @@ static void exception_alt(VALUE obj, int depth, Out out) {
|
|
|
332
333
|
if (0 < out->opts->dump_opts.after_size) {
|
|
333
334
|
APPEND_CHARS(out->cur, out->opts->dump_opts.after_sep, out->opts->dump_opts.after_size);
|
|
334
335
|
}
|
|
335
|
-
|
|
336
|
+
bt = rb_funcall(obj, backtrace_id, 0);
|
|
337
|
+
if (T_ARRAY == rb_type(bt)) {
|
|
338
|
+
dump_array(bt, depth, out, false);
|
|
339
|
+
} else {
|
|
340
|
+
oj_dump_nil(Qnil, depth, out, false);
|
|
341
|
+
}
|
|
336
342
|
fill_indent(out, depth);
|
|
337
343
|
*out->cur++ = '}';
|
|
338
344
|
*out->cur = '\0';
|
data/ext/oj/extconf.rb
CHANGED
data/ext/oj/fast.c
CHANGED
|
@@ -105,6 +105,22 @@ inline static void next_non_white(ParseInfo pi) {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
inline static bool is_value_end(char c) {
|
|
109
|
+
switch (c) {
|
|
110
|
+
case '\0':
|
|
111
|
+
case ' ':
|
|
112
|
+
case '\t':
|
|
113
|
+
case '\f':
|
|
114
|
+
case '\n':
|
|
115
|
+
case '\r':
|
|
116
|
+
case '/':
|
|
117
|
+
case ',':
|
|
118
|
+
case ']':
|
|
119
|
+
case '}': return true;
|
|
120
|
+
default: return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
inline static char *ulong_fill(char *s, size_t num) {
|
|
109
125
|
char buf[32];
|
|
110
126
|
char *b = buf + sizeof(buf) - 1;
|
|
@@ -463,27 +479,42 @@ static Leaf read_num(ParseInfo pi) {
|
|
|
463
479
|
char *start = pi->s;
|
|
464
480
|
int type = T_FIXNUM;
|
|
465
481
|
Leaf leaf;
|
|
482
|
+
int dcnt = 0;
|
|
483
|
+
int ecnt = 1;
|
|
466
484
|
|
|
467
|
-
|
|
485
|
+
// read_next() sends a leading '+' here as well, and Oj.load() takes one.
|
|
486
|
+
if ('-' == *pi->s || '+' == *pi->s) {
|
|
468
487
|
pi->s++;
|
|
469
488
|
}
|
|
470
489
|
// digits
|
|
471
490
|
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
|
491
|
+
dcnt++;
|
|
472
492
|
}
|
|
473
493
|
if ('.' == *pi->s) {
|
|
474
494
|
type = T_FLOAT;
|
|
475
495
|
pi->s++;
|
|
476
496
|
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
|
497
|
+
dcnt++;
|
|
477
498
|
}
|
|
478
499
|
}
|
|
479
500
|
if ('e' == *pi->s || 'E' == *pi->s) {
|
|
501
|
+
// An exponent makes it a float whether or not a '.' was seen. Without
|
|
502
|
+
// this leaf_fixnum_value() stops reading at the 'e' and the exponent is
|
|
503
|
+
// dropped.
|
|
504
|
+
type = T_FLOAT;
|
|
480
505
|
pi->s++;
|
|
481
506
|
if ('-' == *pi->s || '+' == *pi->s) {
|
|
482
507
|
pi->s++;
|
|
483
508
|
}
|
|
484
|
-
for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
|
509
|
+
for (ecnt = 0; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
|
|
510
|
+
ecnt++;
|
|
485
511
|
}
|
|
486
512
|
}
|
|
513
|
+
// A float is converted with rb_cstr_to_dbl(), which has to be given
|
|
514
|
+
// something it can convert.
|
|
515
|
+
if (T_FLOAT == type && (0 == dcnt || 0 == ecnt || !is_value_end(*pi->s))) {
|
|
516
|
+
raise_error("invalid number", pi->str, pi->s);
|
|
517
|
+
}
|
|
487
518
|
leaf = leaf_new(pi->doc, type);
|
|
488
519
|
leaf->str = start;
|
|
489
520
|
|
|
@@ -670,7 +701,11 @@ static void doc_free(Doc doc) {
|
|
|
670
701
|
static VALUE protect_open_proc(VALUE x) {
|
|
671
702
|
ParseInfo pi = (ParseInfo)x;
|
|
672
703
|
|
|
673
|
-
pi->doc->data
|
|
704
|
+
pi->doc->data = read_next(pi); // parse
|
|
705
|
+
// read_obj() and read_array() terminate each value where it ends. The top
|
|
706
|
+
// level value has no closing delimiter to do that on, so a number leaf runs
|
|
707
|
+
// to the end of the document unless it is terminated here.
|
|
708
|
+
*pi->s = '\0';
|
|
674
709
|
*pi->doc->where = pi->doc->data;
|
|
675
710
|
pi->doc->where = pi->doc->where_path;
|
|
676
711
|
if (rb_block_given_p()) {
|
|
@@ -872,7 +907,7 @@ static bool key_match(const char *pat, const char *key, int plen) {
|
|
|
872
907
|
static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path) {
|
|
873
908
|
Leaf leaf = *lp;
|
|
874
909
|
|
|
875
|
-
if (MAX_STACK <= lp - stack) {
|
|
910
|
+
if (MAX_STACK <= (lp + 1) - stack) {
|
|
876
911
|
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
|
877
912
|
}
|
|
878
913
|
if ('\0' != *path) {
|
|
@@ -972,7 +1007,7 @@ static void each_leaf(Doc doc, VALUE self) {
|
|
|
972
1007
|
}
|
|
973
1008
|
|
|
974
1009
|
static int move_step(Doc doc, const char *path, int loc) {
|
|
975
|
-
if (MAX_STACK <= doc->where - doc->where_path) {
|
|
1010
|
+
if (MAX_STACK <= (doc->where + 1) - doc->where_path) {
|
|
976
1011
|
rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
|
|
977
1012
|
}
|
|
978
1013
|
if ('\0' == *path) {
|
data/ext/oj/intern.c
CHANGED
|
@@ -97,6 +97,8 @@ static const rb_data_type_t oj_cache_type = {
|
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
void oj_hash_init(void) {
|
|
100
|
+
oj_hash_seed_init();
|
|
101
|
+
|
|
100
102
|
VALUE cache_class = rb_define_class_under(Oj, "Cache", rb_cObject);
|
|
101
103
|
rb_undef_alloc_func(cache_class);
|
|
102
104
|
|
|
@@ -151,7 +153,7 @@ ID oj_attr_intern(const char *key, size_t len) {
|
|
|
151
153
|
static uint64_t hash_calc(const uint8_t *key, size_t len) {
|
|
152
154
|
const uint8_t *end = key + len;
|
|
153
155
|
const uint8_t *endless = key + (len & 0xFFFFFFFC);
|
|
154
|
-
uint64_t h = (uint64_t)len;
|
|
156
|
+
uint64_t h = (uint64_t)len ^ oj_hash_seed;
|
|
155
157
|
uint64_t k;
|
|
156
158
|
|
|
157
159
|
while (key < endless) {
|
|
@@ -188,6 +190,9 @@ static VALUE resolve_classname(VALUE mod, const char *classname, int auto_define
|
|
|
188
190
|
|
|
189
191
|
if (rb_const_defined_at(mod, ci)) {
|
|
190
192
|
clas = rb_const_get_at(mod, ci);
|
|
193
|
+
if (!RB_TYPE_P(clas, T_CLASS) && !RB_TYPE_P(clas, T_MODULE)) {
|
|
194
|
+
clas = Qundef;
|
|
195
|
+
}
|
|
191
196
|
} else if (auto_define) {
|
|
192
197
|
clas = rb_define_class_under(mod, classname, oj_bag_class);
|
|
193
198
|
} else {
|
data/ext/oj/object.c
CHANGED
|
@@ -31,7 +31,7 @@ inline static long read_long(const char *str, size_t len) {
|
|
|
31
31
|
static VALUE calc_hash_key(ParseInfo pi, Val kval, char k1) {
|
|
32
32
|
volatile VALUE rkey;
|
|
33
33
|
|
|
34
|
-
if (':' == k1) {
|
|
34
|
+
if (':' == k1 && 0 < kval->klen) {
|
|
35
35
|
return ID2SYM(rb_intern3(kval->key + 1, kval->klen - 1, oj_utf8_encoding));
|
|
36
36
|
}
|
|
37
37
|
if (Yes == pi->options.sym_key) {
|
|
@@ -54,7 +54,7 @@ static VALUE str_to_value(ParseInfo pi, const char *str, size_t len, const char
|
|
|
54
54
|
} else if (pi->circ_array && 3 <= len && '^' == *orig && 'r' == orig[1]) {
|
|
55
55
|
long i = read_long(str + 2, len - 2);
|
|
56
56
|
|
|
57
|
-
if (0
|
|
57
|
+
if (0 >= i) {
|
|
58
58
|
oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "not a valid ID number");
|
|
59
59
|
return Qnil;
|
|
60
60
|
}
|
|
@@ -207,7 +207,7 @@ oj_parse_xml_time(const char *str, int len) {
|
|
|
207
207
|
|
|
208
208
|
static int hat_cstr(ParseInfo pi, Val parent, Val kval, const char *str, size_t len) {
|
|
209
209
|
const char *key = kval->key;
|
|
210
|
-
|
|
210
|
+
size_t klen = kval->klen;
|
|
211
211
|
|
|
212
212
|
if (2 == klen) {
|
|
213
213
|
switch (key[1]) {
|
|
@@ -230,7 +230,12 @@ static int hat_cstr(ParseInfo pi, Val parent, Val kval, const char *str, size_t
|
|
|
230
230
|
parent->odd_args = oj_odd_alloc_args(odd);
|
|
231
231
|
break;
|
|
232
232
|
}
|
|
233
|
-
case 'm':
|
|
233
|
+
case 'm':
|
|
234
|
+
if (0 == len) {
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
parent->val = ID2SYM(rb_intern3(str + 1, len - 1, oj_utf8_encoding));
|
|
238
|
+
break;
|
|
234
239
|
case 's': parent->val = rb_utf8_str_new(str, len); break;
|
|
235
240
|
case 'c': // class
|
|
236
241
|
{
|
|
@@ -395,7 +400,7 @@ void oj_set_obj_ivar(Val parent, Val kval, VALUE value) {
|
|
|
395
400
|
|
|
396
401
|
static void hash_set_cstr(ParseInfo pi, Val kval, const char *str, size_t len, const char *orig) {
|
|
397
402
|
const char *key = kval->key;
|
|
398
|
-
|
|
403
|
+
size_t klen = kval->klen;
|
|
399
404
|
Val parent = stack_peek(&pi->stack);
|
|
400
405
|
volatile VALUE rval = Qnil;
|
|
401
406
|
|
|
@@ -437,7 +442,7 @@ WHICH_TYPE:
|
|
|
437
442
|
if (0 != oj_odd_set_arg(parent->odd_args, kval->key, kval->klen, rval)) {
|
|
438
443
|
char buf[256];
|
|
439
444
|
|
|
440
|
-
if (
|
|
445
|
+
if (sizeof(buf) - 1 <= klen) {
|
|
441
446
|
klen = sizeof(buf) - 2;
|
|
442
447
|
}
|
|
443
448
|
memcpy(buf, key, klen);
|
|
@@ -466,7 +471,7 @@ WHICH_TYPE:
|
|
|
466
471
|
|
|
467
472
|
static void hash_set_num(ParseInfo pi, Val kval, NumInfo ni) {
|
|
468
473
|
const char *key = kval->key;
|
|
469
|
-
|
|
474
|
+
size_t klen = kval->klen;
|
|
470
475
|
Val parent = stack_peek(&pi->stack);
|
|
471
476
|
volatile VALUE rval = Qnil;
|
|
472
477
|
|
|
@@ -506,7 +511,7 @@ WHICH_TYPE:
|
|
|
506
511
|
if (0 != oj_odd_set_arg(parent->odd_args, key, klen, rval)) {
|
|
507
512
|
char buf[256];
|
|
508
513
|
|
|
509
|
-
if (
|
|
514
|
+
if (sizeof(buf) - 1 <= klen) {
|
|
510
515
|
klen = sizeof(buf) - 2;
|
|
511
516
|
}
|
|
512
517
|
memcpy(buf, key, klen);
|
|
@@ -535,7 +540,7 @@ WHICH_TYPE:
|
|
|
535
540
|
|
|
536
541
|
static void hash_set_value(ParseInfo pi, Val kval, VALUE value) {
|
|
537
542
|
const char *key = kval->key;
|
|
538
|
-
|
|
543
|
+
size_t klen = kval->klen;
|
|
539
544
|
Val parent = stack_peek(&pi->stack);
|
|
540
545
|
|
|
541
546
|
WHICH_TYPE:
|
|
@@ -591,7 +596,7 @@ WHICH_TYPE:
|
|
|
591
596
|
} else if (0 != oj_odd_set_arg(parent->odd_args, key, klen, value)) {
|
|
592
597
|
char buf[256];
|
|
593
598
|
|
|
594
|
-
if (
|
|
599
|
+
if (sizeof(buf) - 1 <= klen) {
|
|
595
600
|
klen = sizeof(buf) - 2;
|
|
596
601
|
}
|
|
597
602
|
memcpy(buf, key, klen);
|
data/ext/oj/odd.c
CHANGED
|
@@ -166,7 +166,11 @@ Odd oj_get_oddc(const char *classname, size_t len) {
|
|
|
166
166
|
if (len == odd->clen && 0 == strncmp(classname, odd->classname, len)) {
|
|
167
167
|
return odd;
|
|
168
168
|
}
|
|
169
|
-
|
|
169
|
+
// The module test needs a "::" after the module name, so the name has
|
|
170
|
+
// to be shorter than what it is compared against. Without that the
|
|
171
|
+
// comparison and the character after it both run past len.
|
|
172
|
+
if (odd->is_module && odd->clen < len && 0 == strncmp(odd->classname, classname, odd->clen) &&
|
|
173
|
+
':' == classname[odd->clen]) {
|
|
170
174
|
return odd;
|
|
171
175
|
}
|
|
172
176
|
}
|
|
@@ -195,7 +199,7 @@ int oj_odd_set_arg(OddArgs args, const char *key, size_t klen, VALUE value) {
|
|
|
195
199
|
int i;
|
|
196
200
|
|
|
197
201
|
for (i = args->odd->attr_cnt, np = args->odd->attr_names, vp = args->args; 0 < i; i--, np++, vp++) {
|
|
198
|
-
if (
|
|
202
|
+
if (klen == strlen(*np) && 0 == memcmp(key, *np, klen)) {
|
|
199
203
|
*vp = value;
|
|
200
204
|
return 0;
|
|
201
205
|
}
|