nosj 0.3.2 → 0.4.1
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 +55 -0
- data/Cargo.lock +41 -41
- data/README.md +19 -1
- data/ext/nosj/Cargo.toml +6 -6
- data/ext/nosj/fuzz/Cargo.toml +1 -1
- data/ext/nosj/src/errors.rs +3 -1
- data/ext/nosj/src/files.rs +21 -20
- data/ext/nosj/src/gen/errors.rs +12 -10
- data/ext/nosj/src/gen/mod.rs +50 -49
- data/ext/nosj/src/gen/ruby.rs +54 -22
- data/ext/nosj/src/gen/walker.rs +56 -73
- data/ext/nosj/src/lazy.rs +60 -50
- data/ext/nosj/src/lib.rs +17 -0
- data/ext/nosj/src/lines.rs +27 -17
- data/ext/nosj/src/parse.rs +10 -6
- data/ext/nosj/src/patch.rs +25 -15
- data/ext/nosj/src/pointer.rs +9 -11
- data/ext/nosj/src/reformat.rs +39 -25
- data/ext/nosj/src/sink.rs +13 -16
- data/ext/nosj/src/state.rs +60 -14
- data/ext/nosj/src/stats.rs +2 -3
- data/lib/nosj/version.rb +1 -1
- data/lib/nosj.rb +2 -1
- 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: 938bbe04908f0865a510008040c4a1bf8f1e4b1cb80821de3757e3ac078f2e47
|
|
4
|
+
data.tar.gz: feea0d89e6f7559642127a2333d01cc3f93b1d3763e54f6ed620b596a7102d30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b1ff3aef1231141f9333030016f1ff308a81ca27a1ad8318fadf2ce42c77869756c5674716485c4b5be4ca948290f4450142c59eed76df2894bde460cd0530bc
|
|
7
|
+
data.tar.gz: 5e40f2cfb493c13b546ef3367e02efafb7eea5697181b0daad9efeed89f06db58b0db798cd5cad2f09555ee900c673753595584829ab333bdc125c45eaeca41a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,58 @@
|
|
|
1
|
+
## [0.4.1] - 2026-09-25
|
|
2
|
+
|
|
3
|
+
- Fixed a crash in `NOSJ.generate`: an object whose `to_json` or
|
|
4
|
+
`to_s` shrank the array being generated (for example with
|
|
5
|
+
`Array#clear`) made the generator read freed memory, usually a
|
|
6
|
+
segfault. The array length is now re-read for every element, like
|
|
7
|
+
the json gem, so elements a callback appends are emitted too.
|
|
8
|
+
- Fixed memory corruption in `NOSJ.splice`: a replacement value whose
|
|
9
|
+
`to_json` modified the source string, deduplicated a frozen String
|
|
10
|
+
subclass (`-str`), or removed entries from the edits hash could make
|
|
11
|
+
splice read freed memory, copying unrelated heap bytes into the
|
|
12
|
+
result or crashing. All values are now generated before the source
|
|
13
|
+
is read.
|
|
14
|
+
- Fixed `NOSJ.lazy` and `NOSJ.each_line` reading freed memory when the
|
|
15
|
+
source is a frozen String subclass (such as
|
|
16
|
+
`ActiveSupport::SafeBuffer`) or a frozen string carrying instance
|
|
17
|
+
variables, and it is deduplicated with `-str` while the lazy
|
|
18
|
+
document is alive or between lines: Ruby swaps such a string's
|
|
19
|
+
buffer, and the old one kept being read (wrong values, or a crash).
|
|
20
|
+
- Fixed a memory leak in `NOSJ.generate` and `NOSJ.write_file`: an
|
|
21
|
+
exception raised by user code the generator calls bypassed its
|
|
22
|
+
cleanup and leaked its output buffer (megabytes per call after large
|
|
23
|
+
documents). Affected: a raising `respond_to?` or
|
|
24
|
+
`respond_to_missing?`, a raising `to_s` on an encoding-conversion
|
|
25
|
+
error, a raising autoload of `JSON::Fragment` (strict and Rails
|
|
26
|
+
modes), and a raising `Errno` constructor for a failed write. The
|
|
27
|
+
exception still propagates unchanged.
|
|
28
|
+
- Fixed: after a `NoMemoryError` in the middle of a parse (or
|
|
29
|
+
`minify`/`reformat`), the next call on that thread aborted the whole
|
|
30
|
+
process. Per-thread parser state is now recovered instead.
|
|
31
|
+
- `NOSJ::Lazy` nodes now take part in generational GC: holding many
|
|
32
|
+
nodes no longer slows down every minor GC (200,000 live nodes: 2.9 ms
|
|
33
|
+
per minor GC before, 0.1 ms now), and they are freed immediately
|
|
34
|
+
when collected.
|
|
35
|
+
- Fixed: lazy documents opened with `allow_trailing_comma: true` or
|
|
36
|
+
`allow_nan: true` could not be walked. `size`, `keys`, `each`, and
|
|
37
|
+
any lookup that missed or stepped over a trailing comma or a `NaN`
|
|
38
|
+
raised `NOSJ::ParserError`, even though `value` worked.
|
|
39
|
+
- `NOSJ.at_pointer`, `NOSJ.at_pointers`, and `NOSJ.at_pointer_file`
|
|
40
|
+
now honor `allow_nan` and `allow_trailing_comma` while resolving the
|
|
41
|
+
pointer, not only when materializing the matched value.
|
|
42
|
+
- Updated the nosj crate to 0.2.2.
|
|
43
|
+
- Updated dependencies, Magnus bumped to 0.9.0.
|
|
44
|
+
|
|
45
|
+
## [0.4.0] - 2026-09-05
|
|
46
|
+
|
|
47
|
+
- Ractors: on Ruby 4.0+, `NOSJ.parse`, `NOSJ.generate`, and every
|
|
48
|
+
other entry point (lazy documents, partial parsing, the file APIs,
|
|
49
|
+
NDJSON, patches, reformatting, statistics) can be called from inside
|
|
50
|
+
a Ractor. Before this release each of them raised
|
|
51
|
+
`Ractor::UnsafeError` outside the main Ractor. Values parsed with
|
|
52
|
+
`freeze: true` are Ractor-shareable, so a document parsed in one
|
|
53
|
+
Ractor can be handed to another without copying.
|
|
54
|
+
- Updated Rust dependencies.
|
|
55
|
+
|
|
1
56
|
## [0.3.2] - 2026-07-19
|
|
2
57
|
|
|
3
58
|
- **IMPORTANT**—Fixed: the precompiled platform gems linked `libruby`
|
data/Cargo.lock
CHANGED
|
@@ -17,9 +17,9 @@ dependencies = [
|
|
|
17
17
|
|
|
18
18
|
[[package]]
|
|
19
19
|
name = "aho-corasick"
|
|
20
|
-
version = "1.1.
|
|
20
|
+
version = "1.1.5"
|
|
21
21
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
22
|
-
checksum = "
|
|
22
|
+
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
|
23
23
|
dependencies = [
|
|
24
24
|
"memchr",
|
|
25
25
|
]
|
|
@@ -44,9 +44,9 @@ dependencies = [
|
|
|
44
44
|
|
|
45
45
|
[[package]]
|
|
46
46
|
name = "bitflags"
|
|
47
|
-
version = "2.13.
|
|
47
|
+
version = "2.13.2"
|
|
48
48
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
49
|
-
checksum = "
|
|
49
|
+
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
|
50
50
|
|
|
51
51
|
[[package]]
|
|
52
52
|
name = "cexpr"
|
|
@@ -59,15 +59,15 @@ dependencies = [
|
|
|
59
59
|
|
|
60
60
|
[[package]]
|
|
61
61
|
name = "cfg-if"
|
|
62
|
-
version = "1.0.
|
|
62
|
+
version = "1.0.5"
|
|
63
63
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
64
|
-
checksum = "
|
|
64
|
+
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
|
65
65
|
|
|
66
66
|
[[package]]
|
|
67
67
|
name = "clang-sys"
|
|
68
|
-
version = "1.
|
|
68
|
+
version = "1.9.1"
|
|
69
69
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
70
|
-
checksum = "
|
|
70
|
+
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
|
71
71
|
dependencies = [
|
|
72
72
|
"glob",
|
|
73
73
|
"libc",
|
|
@@ -76,15 +76,15 @@ dependencies = [
|
|
|
76
76
|
|
|
77
77
|
[[package]]
|
|
78
78
|
name = "either"
|
|
79
|
-
version = "1.
|
|
79
|
+
version = "1.18.0"
|
|
80
80
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
81
|
-
checksum = "
|
|
81
|
+
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
|
82
82
|
|
|
83
83
|
[[package]]
|
|
84
84
|
name = "fast-float2"
|
|
85
|
-
version = "0.2.
|
|
85
|
+
version = "0.2.4"
|
|
86
86
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
87
|
-
checksum = "
|
|
87
|
+
checksum = "c6e8948ce679d00a02a94739ea185595dca7118ed04feb991127e443bd3d761f"
|
|
88
88
|
|
|
89
89
|
[[package]]
|
|
90
90
|
name = "getrandom"
|
|
@@ -100,9 +100,9 @@ dependencies = [
|
|
|
100
100
|
|
|
101
101
|
[[package]]
|
|
102
102
|
name = "glob"
|
|
103
|
-
version = "0.3.
|
|
103
|
+
version = "0.3.4"
|
|
104
104
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
105
|
-
checksum = "
|
|
105
|
+
checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
|
|
106
106
|
|
|
107
107
|
[[package]]
|
|
108
108
|
name = "itertools"
|
|
@@ -121,9 +121,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|
|
121
121
|
|
|
122
122
|
[[package]]
|
|
123
123
|
name = "libc"
|
|
124
|
-
version = "0.2.
|
|
124
|
+
version = "0.2.189"
|
|
125
125
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
126
|
-
checksum = "
|
|
126
|
+
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
|
127
127
|
|
|
128
128
|
[[package]]
|
|
129
129
|
name = "libloading"
|
|
@@ -137,9 +137,9 @@ dependencies = [
|
|
|
137
137
|
|
|
138
138
|
[[package]]
|
|
139
139
|
name = "magnus"
|
|
140
|
-
version = "0.
|
|
140
|
+
version = "0.9.0"
|
|
141
141
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
142
|
-
checksum = "
|
|
142
|
+
checksum = "379d50b6eea4ca84074ccd3e551eb9b07f7865194d5b3198325317e9ef78e5e9"
|
|
143
143
|
dependencies = [
|
|
144
144
|
"magnus-macros",
|
|
145
145
|
"rb-sys",
|
|
@@ -149,9 +149,9 @@ dependencies = [
|
|
|
149
149
|
|
|
150
150
|
[[package]]
|
|
151
151
|
name = "magnus-macros"
|
|
152
|
-
version = "0.
|
|
152
|
+
version = "0.9.0"
|
|
153
153
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
154
|
-
checksum = "
|
|
154
|
+
checksum = "4ca70566af98184bb92d4d9853daaf533c1a3418b9570d0547ea44970035f3fe"
|
|
155
155
|
dependencies = [
|
|
156
156
|
"proc-macro2",
|
|
157
157
|
"quote",
|
|
@@ -191,16 +191,16 @@ dependencies = [
|
|
|
191
191
|
|
|
192
192
|
[[package]]
|
|
193
193
|
name = "nosj"
|
|
194
|
-
version = "0.2.
|
|
194
|
+
version = "0.2.2"
|
|
195
195
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
196
|
-
checksum = "
|
|
196
|
+
checksum = "3d398b0019f2d8bf39b10de6038451b0f3d96a9e3111748097192d8f57d6f120"
|
|
197
197
|
dependencies = [
|
|
198
198
|
"fast-float2",
|
|
199
199
|
]
|
|
200
200
|
|
|
201
201
|
[[package]]
|
|
202
202
|
name = "nosj_native"
|
|
203
|
-
version = "0.
|
|
203
|
+
version = "0.4.1"
|
|
204
204
|
dependencies = [
|
|
205
205
|
"ahash",
|
|
206
206
|
"magnus",
|
|
@@ -217,18 +217,18 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
|
|
217
217
|
|
|
218
218
|
[[package]]
|
|
219
219
|
name = "proc-macro2"
|
|
220
|
-
version = "1.0.
|
|
220
|
+
version = "1.0.107"
|
|
221
221
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
222
|
-
checksum = "
|
|
222
|
+
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
|
223
223
|
dependencies = [
|
|
224
224
|
"unicode-ident",
|
|
225
225
|
]
|
|
226
226
|
|
|
227
227
|
[[package]]
|
|
228
228
|
name = "quote"
|
|
229
|
-
version = "1.0.
|
|
229
|
+
version = "1.0.47"
|
|
230
230
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
231
|
-
checksum = "
|
|
231
|
+
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
|
232
232
|
dependencies = [
|
|
233
233
|
"proc-macro2",
|
|
234
234
|
]
|
|
@@ -241,18 +241,18 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
|
241
241
|
|
|
242
242
|
[[package]]
|
|
243
243
|
name = "rb-sys"
|
|
244
|
-
version = "0.9.
|
|
244
|
+
version = "0.9.130"
|
|
245
245
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
246
|
-
checksum = "
|
|
246
|
+
checksum = "02faf625bb10ba893e3ae620f19c9fb1b5f8fcae0fe4eb86bb3f2230fad75edb"
|
|
247
247
|
dependencies = [
|
|
248
248
|
"rb-sys-build",
|
|
249
249
|
]
|
|
250
250
|
|
|
251
251
|
[[package]]
|
|
252
252
|
name = "rb-sys-build"
|
|
253
|
-
version = "0.9.
|
|
253
|
+
version = "0.9.130"
|
|
254
254
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
255
|
-
checksum = "
|
|
255
|
+
checksum = "05be6f9c86fe5482808162826f6c047d093b3670582296c770de1d94f8066694"
|
|
256
256
|
dependencies = [
|
|
257
257
|
"bindgen",
|
|
258
258
|
"lazy_static",
|
|
@@ -271,9 +271,9 @@ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
|
271
271
|
|
|
272
272
|
[[package]]
|
|
273
273
|
name = "regex"
|
|
274
|
-
version = "1.13.
|
|
274
|
+
version = "1.13.1"
|
|
275
275
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
-
checksum = "
|
|
276
|
+
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
|
277
277
|
dependencies = [
|
|
278
278
|
"aho-corasick",
|
|
279
279
|
"memchr",
|
|
@@ -283,9 +283,9 @@ dependencies = [
|
|
|
283
283
|
|
|
284
284
|
[[package]]
|
|
285
285
|
name = "regex-automata"
|
|
286
|
-
version = "0.4.
|
|
286
|
+
version = "0.4.18"
|
|
287
287
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
288
|
-
checksum = "
|
|
288
|
+
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
|
289
289
|
dependencies = [
|
|
290
290
|
"aho-corasick",
|
|
291
291
|
"memchr",
|
|
@@ -335,9 +335,9 @@ dependencies = [
|
|
|
335
335
|
|
|
336
336
|
[[package]]
|
|
337
337
|
name = "unicode-ident"
|
|
338
|
-
version = "1.0.
|
|
338
|
+
version = "1.0.26"
|
|
339
339
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
340
|
-
checksum = "
|
|
340
|
+
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
|
341
341
|
|
|
342
342
|
[[package]]
|
|
343
343
|
name = "version_check"
|
|
@@ -368,18 +368,18 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
|
|
368
368
|
|
|
369
369
|
[[package]]
|
|
370
370
|
name = "zerocopy"
|
|
371
|
-
version = "0.8.
|
|
371
|
+
version = "0.8.58"
|
|
372
372
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
373
|
-
checksum = "
|
|
373
|
+
checksum = "c17e8fafad82b542ff3717217ecdc736231b59e387768c9630123b4ce4d2db44"
|
|
374
374
|
dependencies = [
|
|
375
375
|
"zerocopy-derive",
|
|
376
376
|
]
|
|
377
377
|
|
|
378
378
|
[[package]]
|
|
379
379
|
name = "zerocopy-derive"
|
|
380
|
-
version = "0.8.
|
|
380
|
+
version = "0.8.58"
|
|
381
381
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
382
|
-
checksum = "
|
|
382
|
+
checksum = "595f56e044df4f46a0c9a626f65c3d99eb8488f7e8a8baa12dd76326d9710bf2"
|
|
383
383
|
dependencies = [
|
|
384
384
|
"proc-macro2",
|
|
385
385
|
"quote",
|
data/README.md
CHANGED
|
@@ -16,6 +16,7 @@ faster than Yajl—[see Benchmarks](#benchmarks).
|
|
|
16
16
|
- It does **byte-splicing edits**: `NOSJ.splice` changes a field inside a passing payload 10–51× faster than parse-mutate-generate, and RFC 6902 JSON Patch / RFC 7386 merge patch apply straight to the raw string.
|
|
17
17
|
- It is **great to debug with**: parse errors carry line, column, and a caret snippet pointing at the break, and `NOSJ.stats` X-rays a mystery blob (depth, value counts, key histogram) faster than parsing it.
|
|
18
18
|
- It accelerates a **Rails** application in both encoding and decoding.
|
|
19
|
+
- It is **Ractor-safe**: call it from any Ractor on Ruby 4.0+, and parse with `freeze: true` to get shareable results.
|
|
19
20
|
- It comes **precompiled** (platform gems built with per-platform optimizations,
|
|
20
21
|
nothing to compile on install).
|
|
21
22
|
- Otherwise, same API and option names as gem json.
|
|
@@ -150,7 +151,8 @@ Example: an early field resolves in ~0.35µs where `JSON.parse(json).dig(...)`
|
|
|
150
151
|
costs ~980µs on the same document—three orders of magnitude. A field
|
|
151
152
|
at the far end of a 570 KB document costs ~71µs, still 13× faster
|
|
152
153
|
than parse-then-dig. Misses return nil; matched subtrees materialize
|
|
153
|
-
with the same options as `parse` (`symbolize_names:`, `freeze:`)
|
|
154
|
+
with the same options as `parse` (`symbolize_names:`, `freeze:`), and
|
|
155
|
+
`allow_nan:`/`allow_trailing_comma:` govern the walk to them too.
|
|
154
156
|
|
|
155
157
|
### Files API
|
|
156
158
|
|
|
@@ -300,6 +302,22 @@ NOSJ.parse(%({\n "a": 1,\n "b": }))
|
|
|
300
302
|
# ^
|
|
301
303
|
```
|
|
302
304
|
|
|
305
|
+
### Ractors
|
|
306
|
+
|
|
307
|
+
On Ruby 4.0+, every entry point works inside a Ractor: parse and
|
|
308
|
+
generate, lazy documents, partial parsing, the file APIs, NDJSON,
|
|
309
|
+
patches, reformatting, and statistics.
|
|
310
|
+
|
|
311
|
+
```ruby
|
|
312
|
+
workers = Array.new(4) do
|
|
313
|
+
Ractor.new(payload) { |src| NOSJ.parse(src, freeze: true) }
|
|
314
|
+
end
|
|
315
|
+
workers.map(&:value)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`freeze: true` returns deeply frozen, Ractor-shareable values, so a
|
|
319
|
+
document parsed in one Ractor can be handed to another without a copy.
|
|
320
|
+
|
|
303
321
|
## Benchmarks
|
|
304
322
|
|
|
305
323
|
Every installed JSON gem, benchmark-ips: AWS EC2 c7a.2xlarge (AMD EPYC 9R14, Zen 4), Ruby 4.0.6 + YJIT, json 2.21.1, Oj 3.17.4, RapidJSON 0.4.0, FastJsonparser 0.6.0, Yajl 1.4.3, PGO build, 2026-07-16. `×N` = times slower than nosj.
|
data/ext/nosj/Cargo.toml
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# required by lib/nosj.rb as "nosj/nosj" (Init_nosj set explicitly in
|
|
6
6
|
# lib.rs). Gem name, module, and API are plain nosj.
|
|
7
7
|
name = "nosj_native"
|
|
8
|
-
version = "0.
|
|
8
|
+
version = "0.4.1"
|
|
9
9
|
edition = "2021"
|
|
10
10
|
authors = ["Yaroslav Markin <yaroslav@markin.net>"]
|
|
11
11
|
license = "MIT"
|
|
@@ -16,13 +16,13 @@ name = "nosj"
|
|
|
16
16
|
crate-type = ["cdylib", "rlib"]
|
|
17
17
|
|
|
18
18
|
[dependencies]
|
|
19
|
-
magnus = { version = "0.
|
|
20
|
-
rb-sys = { version = "0.9.
|
|
21
|
-
ahash = "0.8.
|
|
19
|
+
magnus = { version = "0.9", features = ["rb-sys"] }
|
|
20
|
+
rb-sys = { version = "0.9.130", default-features = false }
|
|
21
|
+
ahash = "0.8.12"
|
|
22
22
|
# Read-only file mapping for the file entry points (load_lazy_file,
|
|
23
23
|
# at_pointer_file, dig_file): pages never touched are never read.
|
|
24
|
-
memmap2 = "0.9"
|
|
24
|
+
memmap2 = "0.9.11"
|
|
25
25
|
# First-party SIMD JSON parse/generate library (github.com/yaroslav/nosj).
|
|
26
26
|
# For coordinated crate+gem work, temporarily flip to
|
|
27
27
|
# { path = "../../../nosj" } and restore before any commit or release.
|
|
28
|
-
nosj = "0.2.
|
|
28
|
+
nosj = "0.2.2"
|
data/ext/nosj/fuzz/Cargo.toml
CHANGED
|
@@ -13,7 +13,7 @@ libfuzzer-sys = "0.4"
|
|
|
13
13
|
# crate. Only this harness enables "embed" (rb-sys/link-ruby): fuzz
|
|
14
14
|
# binaries host their own VM, while the extension must never link
|
|
15
15
|
# libruby (its symbols resolve from the host process at load time).
|
|
16
|
-
magnus = { version = "0.
|
|
16
|
+
magnus = { version = "0.9", features = ["embed", "rb-sys"] }
|
|
17
17
|
# The extension as an rlib; its lib target is named `nosj` (the bundle
|
|
18
18
|
# name), so rename the dependency to keep fuzz code readable.
|
|
19
19
|
nosj_ext = { path = "..", package = "nosj_native" }
|
data/ext/nosj/src/errors.rs
CHANGED
|
@@ -16,7 +16,9 @@ use magnus::{Class, Error, ExceptionClass, Module, Object, RModule, RObject, Rub
|
|
|
16
16
|
/// extension is loaded without lib/nosj.rb).
|
|
17
17
|
pub(crate) fn nosj_exception(ruby: &Ruby, name: &str) -> ExceptionClass {
|
|
18
18
|
let lookup = || -> Result<ExceptionClass, Error> {
|
|
19
|
-
|
|
19
|
+
// A lookup, not define_module: defining is main-Ractor work, and
|
|
20
|
+
// this runs wherever a parse fails.
|
|
21
|
+
let m: RModule = ruby.class_object().const_get("NOSJ")?;
|
|
20
22
|
m.const_get(name)
|
|
21
23
|
};
|
|
22
24
|
lookup().unwrap_or_else(|_| ruby.exception_runtime_error())
|
data/ext/nosj/src/files.rs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//! I/O failures raise the mapped `Errno::*` exception, like `File`
|
|
11
11
|
//! methods do.
|
|
12
12
|
|
|
13
|
-
use std::cell::
|
|
13
|
+
use std::cell::Cell;
|
|
14
14
|
use std::fs;
|
|
15
15
|
use std::io::Read;
|
|
16
16
|
|
|
@@ -22,14 +22,15 @@ use crate::gen;
|
|
|
22
22
|
use crate::lazy::{self, DocBytes};
|
|
23
23
|
use crate::parse::{err, materialize, materialize_at, parse_native_opts, span_of, ParseNativeOpts};
|
|
24
24
|
use crate::pointer::path_to_pointer;
|
|
25
|
-
use crate::state::
|
|
25
|
+
use crate::state::{with_pull_state, with_taken};
|
|
26
26
|
|
|
27
27
|
const NOT_UTF8: &str = "input is not valid UTF-8";
|
|
28
28
|
|
|
29
29
|
thread_local! {
|
|
30
30
|
/// Reused read buffer for `load_file`: capacity survives across
|
|
31
|
-
/// calls, so a hot loop over files allocates nothing
|
|
32
|
-
|
|
31
|
+
/// calls, so a hot loop over files allocates nothing (see
|
|
32
|
+
/// `state::with_taken`).
|
|
33
|
+
static FILE_BUF: Cell<Vec<u8>> = const { Cell::new(Vec::new()) };
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/// On Unix the raw OS error IS the errno `rb_syserr_new` expects.
|
|
@@ -58,6 +59,9 @@ fn errno_of(e: &std::io::Error) -> Option<i32> {
|
|
|
58
59
|
|
|
59
60
|
/// Map an I/O failure onto the matching `Errno::*` exception (class
|
|
60
61
|
/// parity with `File.read`/`File.write`; the message carries the path).
|
|
62
|
+
/// Constructing it runs the class's `initialize` (overridable Ruby), so
|
|
63
|
+
/// the call is protected; a raise there propagates in its place, as it
|
|
64
|
+
/// would from `File.read`.
|
|
61
65
|
fn io_error(ruby: &Ruby, path: &str, e: &std::io::Error) -> Error {
|
|
62
66
|
use magnus::rb_sys::FromRawValue;
|
|
63
67
|
let Some(errno) = errno_of(e) else {
|
|
@@ -66,8 +70,13 @@ fn io_error(ruby: &Ruby, path: &str, e: &std::io::Error) -> Error {
|
|
|
66
70
|
let Ok(cpath) = std::ffi::CString::new(path) else {
|
|
67
71
|
return runtime_error(ruby, format!("{e} - {path}"));
|
|
68
72
|
};
|
|
73
|
+
let raw =
|
|
74
|
+
match magnus::rb_sys::protect(|| unsafe { rb_sys::rb_syserr_new(errno, cpath.as_ptr()) }) {
|
|
75
|
+
Ok(raw) => raw,
|
|
76
|
+
Err(raised) => return raised,
|
|
77
|
+
};
|
|
69
78
|
// SAFETY: rb_syserr_new returns a live Errno exception instance.
|
|
70
|
-
let exc = unsafe { Value::from_raw(
|
|
79
|
+
let exc = unsafe { Value::from_raw(raw) };
|
|
71
80
|
match magnus::Exception::from_value(exc) {
|
|
72
81
|
Some(exc) => exc.into(),
|
|
73
82
|
None => runtime_error(ruby, format!("{e} - {path}")),
|
|
@@ -84,23 +93,16 @@ pub fn load_file_native(
|
|
|
84
93
|
) -> Result<Value, Error> {
|
|
85
94
|
let o = parse_native_opts(ruby, opts)?;
|
|
86
95
|
let p = path.to_string()?;
|
|
87
|
-
FILE_BUF
|
|
88
|
-
let mut buf = cell
|
|
89
|
-
.try_borrow_mut()
|
|
90
|
-
.map_or_else(|_| Vec::new(), |mut b| std::mem::take(&mut *b));
|
|
96
|
+
with_taken(&FILE_BUF, |buf| {
|
|
91
97
|
buf.clear();
|
|
92
|
-
|
|
98
|
+
read_into(buf, &p)
|
|
93
99
|
.map_err(|e| io_error(ruby, &p, &e))
|
|
94
100
|
.and_then(|()| {
|
|
95
|
-
if std::str::from_utf8(
|
|
101
|
+
if std::str::from_utf8(buf).is_err() {
|
|
96
102
|
return Err(err(ruby, NOT_UTF8.into()));
|
|
97
103
|
}
|
|
98
|
-
materialize(ruby,
|
|
99
|
-
})
|
|
100
|
-
if let Ok(mut slot) = cell.try_borrow_mut() {
|
|
101
|
-
*slot = buf;
|
|
102
|
-
}
|
|
103
|
-
result
|
|
104
|
+
materialize(ruby, buf, &o)
|
|
105
|
+
})
|
|
104
106
|
})
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -190,10 +192,9 @@ fn resolve_file_pointer(
|
|
|
190
192
|
o: &ParseNativeOpts,
|
|
191
193
|
) -> Result<Value, Error> {
|
|
192
194
|
with_mapped_file(ruby, path, |map| {
|
|
193
|
-
let resolved =
|
|
194
|
-
let mut state = cell.borrow_mut();
|
|
195
|
+
let resolved = with_pull_state(|state| {
|
|
195
196
|
// SAFETY: UTF-8 checked by with_mapped_file.
|
|
196
|
-
unsafe { nosj::
|
|
197
|
+
unsafe { nosj::pointer_utf8_unchecked_with(&map, pointer, &mut state.bufs, o.popts) }
|
|
197
198
|
});
|
|
198
199
|
match resolved {
|
|
199
200
|
Ok(None) => Ok(ruby.qnil().as_value()),
|
data/ext/nosj/src/gen/errors.rs
CHANGED
|
@@ -7,7 +7,7 @@ use magnus::rb_sys::AsRawValue;
|
|
|
7
7
|
use magnus::value::ReprValue;
|
|
8
8
|
use magnus::{Error, Ruby};
|
|
9
9
|
|
|
10
|
-
use super::ruby::rstring_bytes;
|
|
10
|
+
use super::ruby::{protected_to_s, rstring_bytes};
|
|
11
11
|
use crate::errors::nosj_exception;
|
|
12
12
|
|
|
13
13
|
pub(super) enum GenFail {
|
|
@@ -24,26 +24,28 @@ pub(super) enum GenFail {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/// The exception's `to_s` (its message), matching what the gem embeds
|
|
27
|
-
/// when it wraps a secondary exception.
|
|
28
|
-
|
|
27
|
+
/// when it wraps a secondary exception. Protected: an exception class
|
|
28
|
+
/// may override `to_s`, and a raise there propagates in place of the
|
|
29
|
+
/// GeneratorError (as it does from the gem).
|
|
30
|
+
fn error_message(err: &Error) -> Result<String, Error> {
|
|
29
31
|
if let ErrorType::Exception(exc) = err.error_type() {
|
|
30
|
-
let s =
|
|
32
|
+
let s = protected_to_s(exc.as_value().as_raw())?;
|
|
31
33
|
// Safety: rb_obj_as_string returns a T_STRING; the bytes are
|
|
32
34
|
// copied into an owned String before any further Ruby call.
|
|
33
35
|
let bytes = unsafe { rstring_bytes(s) };
|
|
34
|
-
return String::from_utf8_lossy(bytes).into_owned();
|
|
36
|
+
return Ok(String::from_utf8_lossy(bytes).into_owned());
|
|
35
37
|
}
|
|
36
|
-
err.to_string()
|
|
38
|
+
Ok(err.to_string())
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
pub(super) fn raise_fail(ruby: &Ruby, fail: GenFail) -> Error {
|
|
40
42
|
match fail {
|
|
41
43
|
GenFail::Reraise(err) => err,
|
|
42
44
|
GenFail::Generator(msg) => Error::new(nosj_exception(ruby, "GeneratorError"), msg),
|
|
43
|
-
GenFail::GeneratorFrom(err) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
45
|
+
GenFail::GeneratorFrom(err) => match error_message(&err) {
|
|
46
|
+
Ok(msg) => Error::new(nosj_exception(ruby, "GeneratorError"), msg),
|
|
47
|
+
Err(raised) => raised,
|
|
48
|
+
},
|
|
47
49
|
GenFail::Nesting(limit) => Error::new(
|
|
48
50
|
nosj_exception(ruby, "NestingError"),
|
|
49
51
|
format!(
|