yeptris 0.6.32.1-aarch64-linux → 0.6.33.2-aarch64-linux
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/README.adoc +9 -0
- data/ext/yeptris_native/json_ruby.c +43 -11
- data/lib/yeptris/3.3/native.so +0 -0
- data/lib/yeptris/3.4/native.so +0 -0
- data/lib/yeptris/4.0/native.so +0 -0
- data/lib/yeptris/ffi.rb +5 -0
- data/lib/yeptris/json.rb +48 -2
- data/lib/yeptris/node.rb +12 -3
- data/lib/yeptris/psych/visitors.rb +1 -1
- data/lib/yeptris/yaml.rb +1 -1
- data/lib/yeptris.rb +1 -1
- data/libyeptris.so +0 -0
- data/vendor/libyeptris/CMakeLists.txt +17 -5
- data/vendor/libyeptris/src/yeptris/dom/dom.c +52 -2
- data/vendor/libyeptris/src/yeptris/dom/ytape.c +238 -16
- data/vendor/libyeptris/src/yeptris/emit/emit.c +22 -1
- data/vendor/libyeptris/src/yeptris/emit/style.c +13 -1
- data/vendor/libyeptris/src/yeptris/emit/writer.c +395 -54
- data/vendor/libyeptris/src/yeptris/emit/writer.h +7 -0
- data/vendor/libyeptris/src/yeptris/events/values.c +1 -0
- data/vendor/libyeptris/src/yeptris/parse/engine.c +1178 -127
- data/vendor/libyeptris/src/yeptris/parse/engine.h +3 -0
- data/vendor/libyeptris/src/yeptris/parse/numbers.c +28 -3
- data/vendor/libyeptris/src/yeptris/parse/scalars.h +12 -0
- data/vendor/libyeptris/src/yeptris/parse.c +12 -4
- data/vendor/libyeptris/src/yeptris/scan/scan.c +42 -0
- data/vendor/libyeptris/src/yeptris/scan/scan.h +7 -0
- data/vendor/libyeptris/src/yeptris/tape.c +88 -1
- data/vendor/libyeptris/src/yeptris/tape_in.h +2 -0
- 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: 4f539306a31099d38b0040d34986abe9aecaf7f4f7950f78fbd828002d0d186d
|
|
4
|
+
data.tar.gz: a325b02fec0989a9d21ee8615c7993d05e9a9b2f7062a64560656a4699160109
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ac10a304643b6fa2dd77979974b0809c6032c8918a9c9a9e192667e479338f0b037a45f1c240e20dccb110f354d46854c3cde5414d14a861a9577b353baba83
|
|
7
|
+
data.tar.gz: 1ba4bb2d25f6c2cbca95360336b8a8827533dace201394f8f92c2cea6395743bee00613963aa650ea2e9cafdc4ff647e7a5db4b251b25086a85491bfee6c6349
|
data/README.adoc
CHANGED
|
@@ -5,6 +5,15 @@ https://github.com/leptris/yeptris[libyeptris] — the YAML counterpart
|
|
|
5
5
|
of libleptris. Psych-compatible semantics, one shared library, zero
|
|
6
6
|
compilation at install.
|
|
7
7
|
|
|
8
|
+
Part of the yeptris program: *the best (multiple dimensions)
|
|
9
|
+
YAML/JSON parser/builder ever* — conformance, performance,
|
|
10
|
+
safety/hardening, API surfaces, transport correctness, and trust,
|
|
11
|
+
held simultaneously, in C, Ruby, Rust, and JS. This binding is the
|
|
12
|
+
most complete surface: the Psych drop-in namespace, JSON.parse
|
|
13
|
+
parity with the strict duplicate-key contract, CBOR, and ValueML,
|
|
14
|
+
tracking every libyeptris release in lockstep. The achievement
|
|
15
|
+
program lives in the engine's PLAN.md ("The best-ever program").
|
|
16
|
+
|
|
8
17
|
== Install (development)
|
|
9
18
|
|
|
10
19
|
The gem loads a shared `libyeptris` — from `YEPTRIS_LIB_PATH`, a
|
|
@@ -173,9 +173,19 @@ static VALUE jr_object(jr* j) {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
static VALUE jr_object_body(jr* j, VALUE h_pre) {
|
|
176
|
+
/* The pairs staging buffer MUST live in GC-scanned memory: pairs
|
|
177
|
+
* beyond the current one exist only there once the key/val locals
|
|
178
|
+
* are reused, so a malloc'd buffer makes them invisible to the
|
|
179
|
+
* collector — it can free them mid-parse and recycle their slots,
|
|
180
|
+
* and rb_hash_bulk_insert then inserts garbage (yeptris-ruby#279:
|
|
181
|
+
* the arm64-darwin SIGSEGV whose fault address was document text).
|
|
182
|
+
* The stack array IS scanned, so the bulk fast path is bounded at
|
|
183
|
+
* 31 pairs; beyond that the buffered pairs flush with one
|
|
184
|
+
* bulk_insert and the rest of the object rides rb_hash_aset. */
|
|
176
185
|
VALUE pairs[64];
|
|
177
186
|
VALUE* pv = pairs;
|
|
178
187
|
size_t pcap = 64, pn = 0, heap_cap = 0;
|
|
188
|
+
int aset_rest = 0;
|
|
179
189
|
for (;;) {
|
|
180
190
|
jr_ws(j);
|
|
181
191
|
if (j->i >= j->len || j->p[j->i] != '"') { j->err = -2; goto out; }
|
|
@@ -186,7 +196,7 @@ static VALUE jr_object_body(jr* j, VALUE h_pre) {
|
|
|
186
196
|
j->i++;
|
|
187
197
|
VALUE val = jr_value(j);
|
|
188
198
|
if (j->err) goto out;
|
|
189
|
-
if (yep_ins_mode == YEP_INS_ASET || j->strict_dup) {
|
|
199
|
+
if (yep_ins_mode == YEP_INS_ASET || j->strict_dup || aset_rest) {
|
|
190
200
|
VALUE h = h_pre == Qundef ? (h_pre = HASH_NEW_CAPA(8)) : h_pre;
|
|
191
201
|
if (j->strict_dup && !NIL_P(rb_hash_aref(h, key))) {
|
|
192
202
|
j->err = -3;
|
|
@@ -196,15 +206,21 @@ static VALUE jr_object_body(jr* j, VALUE h_pre) {
|
|
|
196
206
|
rb_hash_aset(h, key, val);
|
|
197
207
|
} else {
|
|
198
208
|
if (pn + 2 > pcap) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
209
|
+
/* buffer full: flush what is staged (no heap staging —
|
|
210
|
+
* see the #279 note above); THIS pair and the rest of
|
|
211
|
+
* the object ride aset — no continue here, the
|
|
212
|
+
* delimiter scan below must still run */
|
|
213
|
+
VALUE h = (h_pre == Qundef) ? (h_pre = HASH_NEW_CAPA((long)(pn / 2) + 8)) : h_pre;
|
|
214
|
+
rb_hash_bulk_insert((long)pn, (const VALUE*)pv, h);
|
|
215
|
+
pn = 0;
|
|
216
|
+
aset_rest = 1;
|
|
217
|
+
}
|
|
218
|
+
if (aset_rest) {
|
|
219
|
+
rb_hash_aset(h_pre, key, val);
|
|
220
|
+
} else {
|
|
221
|
+
pv[pn++] = key;
|
|
222
|
+
pv[pn++] = val;
|
|
205
223
|
}
|
|
206
|
-
pv[pn++] = key;
|
|
207
|
-
pv[pn++] = val;
|
|
208
224
|
}
|
|
209
225
|
jr_ws(j);
|
|
210
226
|
if (j->i >= j->len) { j->err = -2; goto out; }
|
|
@@ -351,7 +367,17 @@ double yep_rb_scan_time(const char* p, size_t len, int n) {
|
|
|
351
367
|
VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup) {
|
|
352
368
|
jr j;
|
|
353
369
|
memset(&j, 0, sizeof(j));
|
|
354
|
-
|
|
370
|
+
/* The parse holds j.p across unbounded allocations, and the GC
|
|
371
|
+
* strategy knobs leave collection ENABLED during the window
|
|
372
|
+
* (arm64's default is none). A compaction moving the source
|
|
373
|
+
* string would dangle every j->p read after the move
|
|
374
|
+
* (yeptris-ruby#279's heap-layout-dependent SIGSEGV class), so
|
|
375
|
+
* the parse runs on a private copy: one memcpy of the input,
|
|
376
|
+
* then no Ruby object backs the cursor. */
|
|
377
|
+
char* own = malloc(len ? len : 1);
|
|
378
|
+
if (own == NULL) rb_raise(rb_eNoMemError, "yeptris native json");
|
|
379
|
+
memcpy(own, p, len);
|
|
380
|
+
j.p = own; j.len = len; j.enc = rb_utf8_encoding();
|
|
355
381
|
j.strict_dup = strict_dup;
|
|
356
382
|
VALUE already = Qtrue;
|
|
357
383
|
if (yep_gc_mode != YEP_GC_NONE) already = rb_gc_disable();
|
|
@@ -359,6 +385,7 @@ VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup) {
|
|
|
359
385
|
jr_ws(&j);
|
|
360
386
|
if (j.i != len && j.err == 0) j.err = -2;
|
|
361
387
|
free(j.scratch);
|
|
388
|
+
free(own);
|
|
362
389
|
if (already == Qfalse) rb_gc_enable();
|
|
363
390
|
if (yep_gc_mode == YEP_GC_START && j.err == 0) rb_gc_start();
|
|
364
391
|
if (j.err == -1) rb_raise(rb_eNoMemError, "yeptris native json");
|
|
@@ -366,6 +393,11 @@ VALUE yep_rb_parse_json(const char* p, size_t len, int strict_dup) {
|
|
|
366
393
|
rb_raise(rb_path2class("Yeptris::ParseError"), "duplicate key \"%s\" in JSON object",
|
|
367
394
|
RSTRING_PTR(j.dup_key));
|
|
368
395
|
}
|
|
369
|
-
if (j.err != 0)
|
|
396
|
+
if (j.err != 0) {
|
|
397
|
+
/* the tape engine's wording ("not strict JSON at byte N") so
|
|
398
|
+
* consumers can pin one error shape across both routes
|
|
399
|
+
* (yeptris-ruby#279's adjacent note) */
|
|
400
|
+
rb_raise(rb_path2class("Yeptris::ParseError"), "not strict JSON at byte %zu", j.i);
|
|
401
|
+
}
|
|
370
402
|
return v;
|
|
371
403
|
}
|
data/lib/yeptris/3.3/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/3.4/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/4.0/native.so
CHANGED
|
Binary file
|
data/lib/yeptris/ffi.rb
CHANGED
|
@@ -228,12 +228,17 @@ module Yeptris
|
|
|
228
228
|
%i[pointer size_t int int pointer pointer], :int
|
|
229
229
|
attach_function :yeptris_marshal_node,
|
|
230
230
|
%i[yeptris_node pointer pointer], :int
|
|
231
|
+
attach_function :yeptris_marshal_node_ex,
|
|
232
|
+
%i[yeptris_node uint32 pointer pointer], :int
|
|
231
233
|
attach_function :yeptris_marshal_free, [:pointer], :void
|
|
232
234
|
# The bulk child drain (#168's quadratic): O(n) iteration for
|
|
233
235
|
# #each/#each_pair. Engines without it fall back to the per-index
|
|
234
236
|
# walk (correct, quadratic).
|
|
235
237
|
CHILDREN_DRAIN = !(@missing ||= []).include?(:yeptris_node_children)
|
|
236
238
|
MARSHAL = !(@missing ||= []).include?(:yeptris_marshal_node)
|
|
239
|
+
# the strict-dup marshal entry (engine 0.6.34+, yeptris-ruby#278):
|
|
240
|
+
# gates the JSON fallback's C route in STRICT mode
|
|
241
|
+
MARSHAL_STRICT_DUP = !(@missing ||= []).include?(:yeptris_marshal_node_ex)
|
|
237
242
|
|
|
238
243
|
# bulk build (TODO.impl/15 phase D): one call raises a document
|
|
239
244
|
BUILD_SCALAR = 1
|
data/lib/yeptris/json.rb
CHANGED
|
@@ -9,10 +9,13 @@ module Yeptris
|
|
|
9
9
|
# keeps the Psych contract for every input (JSON-shaped included),
|
|
10
10
|
# so the two never drift into each other's semantics.
|
|
11
11
|
#
|
|
12
|
-
# Engines, fastest first (
|
|
12
|
+
# Engines, fastest first (all exact — the parity spec runs against
|
|
13
13
|
# whichever is loaded):
|
|
14
14
|
# 1. the native materializer (fused C scan → VALUE; opt-in build)
|
|
15
|
-
# 2. the
|
|
15
|
+
# 2. the marshal engine (#278): strict-JSON parse + Marshal 4.8
|
|
16
|
+
# emission + ONE core Marshal.load — all C, no per-value Ruby
|
|
17
|
+
# (engine 0.6.34+; the strict flag follows the resolved json gem)
|
|
18
|
+
# 3. the record drain + strict conversion walk (always available)
|
|
16
19
|
module JSON
|
|
17
20
|
class Error < ::Yeptris::Error; end
|
|
18
21
|
class ParseError < Error; end
|
|
@@ -39,9 +42,46 @@ module Yeptris
|
|
|
39
42
|
raise ParseError, e.message
|
|
40
43
|
end
|
|
41
44
|
end
|
|
45
|
+
# yeptris-ruby#278: with no Ruby-version-matched native build the
|
|
46
|
+
# pure-Ruby walk measured 0.11x vs JSON.parse on real documents —
|
|
47
|
+
# the marshal engine keeps the fallback C-side end to end.
|
|
48
|
+
return marshal_engine(source, 0) if ::Yeptris::FFI::MARSHAL_STRICT_DUP
|
|
42
49
|
tape_engine(source)
|
|
43
50
|
end
|
|
44
51
|
|
|
52
|
+
# The marshal engine (#278): yeptris_parse_json gates the strict
|
|
53
|
+
# grammar, yeptris_marshal_node_ex writes the Ruby Marshal 4.8
|
|
54
|
+
# stream (STRICT_DUP_KEYS raising on json 3's default contract),
|
|
55
|
+
# and ONE core Marshal.load materializes the graph. Returns nil
|
|
56
|
+
# when the engine cannot serve the route (caller keeps walking);
|
|
57
|
+
# every parse/dup failure raises ParseError.
|
|
58
|
+
def marshal_engine(source, flags)
|
|
59
|
+
begin
|
|
60
|
+
gate = ::Yeptris::Document.parse_json(source)
|
|
61
|
+
rescue ::Yeptris::ParseError => e
|
|
62
|
+
raise ParseError, e.message
|
|
63
|
+
end
|
|
64
|
+
begin
|
|
65
|
+
out_p = ::FFI::MemoryPointer.new(:pointer)
|
|
66
|
+
olen_p = ::FFI::MemoryPointer.new(:size_t)
|
|
67
|
+
st = ::Yeptris::FFI.yeptris_marshal_node_ex(
|
|
68
|
+
::Yeptris::FFI.yeptris_document_root(gate.c_ptr, 0), flags, out_p, olen_p
|
|
69
|
+
)
|
|
70
|
+
if st != ::Yeptris::FFI::OK
|
|
71
|
+
raise ParseError, ::Yeptris::FFI.last_error_message if st == ::Yeptris::FFI::ERROR_PARSE
|
|
72
|
+
|
|
73
|
+
return nil # UNSUPPORTED: merge keys et al. cannot occur in JSON
|
|
74
|
+
end
|
|
75
|
+
buf = out_p.read_pointer
|
|
76
|
+
data = buf.read_bytes(::Yeptris::FFI.read_c_size_t(olen_p))
|
|
77
|
+
data.force_encoding(Encoding::ASCII_8BIT)
|
|
78
|
+
::Yeptris::FFI.yeptris_marshal_free(buf)
|
|
79
|
+
Marshal.load(data)
|
|
80
|
+
ensure
|
|
81
|
+
gate.free
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
45
85
|
# The tape engine (issue #81): ONE call validates and materializes
|
|
46
86
|
# the record stream; the walk reads four bulk columns and slices
|
|
47
87
|
# strings straight out of the caller's source — no gate document,
|
|
@@ -212,6 +252,12 @@ module Yeptris
|
|
|
212
252
|
raise ParseError, e.message
|
|
213
253
|
end
|
|
214
254
|
begin
|
|
255
|
+
if ::Yeptris::FFI::MARSHAL_STRICT_DUP
|
|
256
|
+
routed = marshal_engine(
|
|
257
|
+
source, STRICT_DUPLICATE_KEYS ? ::Yeptris::FFI::MARSHAL_STRICT_DUP_KEYS : 0
|
|
258
|
+
)
|
|
259
|
+
return routed unless routed.nil?
|
|
260
|
+
end
|
|
215
261
|
cols = ::Yeptris::FFI::ValueColumns.new
|
|
216
262
|
st = ::Yeptris::FFI.yeptris_value_drain_columns(
|
|
217
263
|
source, source.bytesize, ::Yeptris::FFI::SCHEMA_12_CORE, cols
|
data/lib/yeptris/node.rb
CHANGED
|
@@ -50,7 +50,7 @@ class Yeptris::Node
|
|
|
50
50
|
|
|
51
51
|
# Scalar content / alias name (UTF-8 String), nil for collections.
|
|
52
52
|
def value
|
|
53
|
-
len =
|
|
53
|
+
len = len_buffer
|
|
54
54
|
ptr = alive { Yeptris::FFI.yeptris_node_value(@c_ptr, len) }
|
|
55
55
|
return nil if ptr.null?
|
|
56
56
|
|
|
@@ -69,9 +69,18 @@ class Yeptris::Node
|
|
|
69
69
|
STYLES[alive { Yeptris::FFI.yeptris_node_style(@c_ptr) }]
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# The out-length receiver: ONE buffer per thread, reused. A fresh
|
|
73
|
+
# FFI::MemoryPointer per accessor call was an allocation storm that
|
|
74
|
+
# stalled the Windows heap under the #182 churn loop (the #504
|
|
75
|
+
# family — the hang's stack sits in FFI::MemoryPointer#initialize,
|
|
76
|
+
# node.rb anchor/tag reads inside Integer#times with GC.start).
|
|
77
|
+
def len_buffer
|
|
78
|
+
Thread.current[:yeptris_len_buf] ||= ::FFI::MemoryPointer.new(:size_t)
|
|
79
|
+
end
|
|
80
|
+
|
|
72
81
|
# Explicit tag URI when present, else nil.
|
|
73
82
|
def tag
|
|
74
|
-
len =
|
|
83
|
+
len = len_buffer
|
|
75
84
|
ptr = alive { Yeptris::FFI.yeptris_node_tag(@c_ptr, len) }
|
|
76
85
|
return nil if ptr.null?
|
|
77
86
|
|
|
@@ -79,7 +88,7 @@ class Yeptris::Node
|
|
|
79
88
|
end
|
|
80
89
|
|
|
81
90
|
def anchor
|
|
82
|
-
len =
|
|
91
|
+
len = len_buffer
|
|
83
92
|
ptr = alive { Yeptris::FFI.yeptris_node_anchor(@c_ptr, len) }
|
|
84
93
|
return nil if ptr.null?
|
|
85
94
|
|
|
@@ -216,7 +216,7 @@ module Yeptris
|
|
|
216
216
|
# explicit !!str tag (single-quoted) so it does not
|
|
217
217
|
# re-load as a merge
|
|
218
218
|
key_node = @tree.new_scalar("<<", :single_quoted)
|
|
219
|
-
key_node.set_tag("
|
|
219
|
+
key_node.set_tag("tag:yaml.org,2002:str")
|
|
220
220
|
m.map_add_node(key_node, visit(v))
|
|
221
221
|
else
|
|
222
222
|
m.map_add(key_text(k), visit(v))
|
data/lib/yeptris/yaml.rb
CHANGED
|
@@ -364,7 +364,7 @@ module Yeptris
|
|
|
364
364
|
# stdlib yaml_tree: a literal chevron key carries the
|
|
365
365
|
# explicit !!str tag so it does not re-load as a merge
|
|
366
366
|
key_node = doc.new_scalar("<<", :single_quoted)
|
|
367
|
-
key_node.set_tag("
|
|
367
|
+
key_node.set_tag("tag:yaml.org,2002:str")
|
|
368
368
|
m.map_add_node(key_node, build(doc, v, seen))
|
|
369
369
|
else
|
|
370
370
|
m.map_add(key_text(k), build(doc, v, seen))
|
data/lib/yeptris.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Yeptris
|
|
4
4
|
# The gem's version lives in the parent namespace's file — the last
|
|
5
5
|
# internal require (yeptris/version) retired with it.
|
|
6
|
-
VERSION = "0.6.
|
|
6
|
+
VERSION = "0.6.33.2".freeze
|
|
7
7
|
|
|
8
8
|
# Informational notices are OPT-IN (yeptris-ruby#217): consumers
|
|
9
9
|
# embed yeptris inside CLIs that assert clean stderr, and a
|
data/libyeptris.so
CHANGED
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
cmake_minimum_required(VERSION 3.20)
|
|
4
4
|
|
|
5
5
|
project(yeptris
|
|
6
|
-
VERSION 0.6.
|
|
6
|
+
VERSION 0.6.33
|
|
7
7
|
DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
|
|
8
8
|
LANGUAGES C CXX
|
|
9
9
|
)
|
|
@@ -98,15 +98,27 @@ if(YEPTRIS_ENABLE_UBSAN)
|
|
|
98
98
|
message(STATUS "UndefinedBehaviorSanitizer: ENABLED (fatal)")
|
|
99
99
|
endif()
|
|
100
100
|
|
|
101
|
-
# libFuzzer implies ASAN (
|
|
101
|
+
# libFuzzer implies ASAN+UBSan (the teptris fuzz.yml pattern: the
|
|
102
|
+
# memory-safety contracts ride the SAME binary as the fuzzer, so every
|
|
103
|
+
# nightly round reports UB at the crash site instead of libFuzzer's
|
|
104
|
+
# "combine with AddressSanitizer" note).
|
|
102
105
|
if(YEPTRIS_ENABLE_FUZZING)
|
|
103
106
|
# instrument everything, but link the fuzzer MAIN only into the
|
|
104
107
|
# harnesses: a global -fsanitize=fuzzer link option demands
|
|
105
108
|
# LLVMFuzzerTestOneInput from every executable (the CLI and the
|
|
106
109
|
# test binaries died on it once CXX also pinned to clang++)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
if(APPLE)
|
|
111
|
+
# Apple ld64 cannot link UBSan'd static archives
|
|
112
|
+
# ("invalid r_symbolnum"): ASan-only locally
|
|
113
|
+
add_compile_options(-fsanitize=fuzzer-no-link,address -fno-omit-frame-pointer -g)
|
|
114
|
+
add_link_options(-fsanitize=address)
|
|
115
|
+
message(STATUS "libFuzzer: ENABLED (implies ASAN; UBSan needs linux ld)")
|
|
116
|
+
else()
|
|
117
|
+
add_compile_options(-fsanitize=fuzzer-no-link,address,undefined
|
|
118
|
+
-fno-sanitize-recover=undefined -fno-omit-frame-pointer -g)
|
|
119
|
+
add_link_options(-fsanitize=address,undefined)
|
|
120
|
+
message(STATUS "libFuzzer: ENABLED (implies ASAN+UBSan)")
|
|
121
|
+
endif()
|
|
110
122
|
endif()
|
|
111
123
|
|
|
112
124
|
# -------------------------------------------------------------------- LTO --
|
|
@@ -392,6 +392,27 @@ int yep_dom_on_event(void* ctx, const yep_event* ev) {
|
|
|
392
392
|
if (d->depth == 0) {
|
|
393
393
|
return -1;
|
|
394
394
|
}
|
|
395
|
+
if (ev->type == YEP_EV_MAP_END && d->map_pending_key[d->depth - 1]) {
|
|
396
|
+
/* an odd child reached the mapping: the unpaired node —
|
|
397
|
+
* possibly an ANCHORED one — used to be dropped here, its
|
|
398
|
+
* anchor lost, and a later alias then emitted a dangling
|
|
399
|
+
* reference the re-parse rejected (the nightly's round
|
|
400
|
+
* 102). Pair it with a null: "k:" with nothing is a null
|
|
401
|
+
* value in every other position. */
|
|
402
|
+
yep_event nv;
|
|
403
|
+
memset(&nv, 0, sizeof(nv));
|
|
404
|
+
nv.type = YEP_EV_SCALAR;
|
|
405
|
+
nv.style = YEP_STYLE_PLAIN;
|
|
406
|
+
nv.implicit = 1;
|
|
407
|
+
uint32_t nid = dom_new_node(d, &nv, YEP_DOM_SCALAR);
|
|
408
|
+
if (nid == UINT32_MAX) {
|
|
409
|
+
return -1;
|
|
410
|
+
}
|
|
411
|
+
uint32_t top = d->stack[d->depth - 1];
|
|
412
|
+
dom_link(d, top, d->pending_key_id[d->depth - 1]);
|
|
413
|
+
dom_link(d, top, nid);
|
|
414
|
+
d->map_pending_key[d->depth - 1] = 0;
|
|
415
|
+
}
|
|
395
416
|
d->depth--;
|
|
396
417
|
return 0;
|
|
397
418
|
|
|
@@ -729,6 +750,7 @@ static int jb_value(jbuilder* b) {
|
|
|
729
750
|
return 0;
|
|
730
751
|
}
|
|
731
752
|
for (;;) {
|
|
753
|
+
size_t member_start = b->i;
|
|
732
754
|
int rc = jb_value(b);
|
|
733
755
|
if (rc != 0) {
|
|
734
756
|
return rc;
|
|
@@ -738,6 +760,14 @@ static int jb_value(jbuilder* b) {
|
|
|
738
760
|
/* a value must be followed by , or } — the map's key
|
|
739
761
|
* slot is pending until the ':' value completes */
|
|
740
762
|
if (b->i < b->len && b->p[b->i] == ':' && d->map_pending_key[d->depth - 1] != 0) {
|
|
763
|
+
/* libyaml's simple-key law rides the JSON-class
|
|
764
|
+
* route too: a key whose colon distance crosses
|
|
765
|
+
* 1024 characters defers to the engine's clean
|
|
766
|
+
* error (run 38143486175) */
|
|
767
|
+
if (b->i - member_start > 1024 &&
|
|
768
|
+
yep_simple_key_chars(b->p + member_start, b->i - member_start) > 1024) {
|
|
769
|
+
return -2;
|
|
770
|
+
}
|
|
741
771
|
b->i++;
|
|
742
772
|
rc = jb_value(b);
|
|
743
773
|
if (rc != 0) {
|
|
@@ -820,8 +850,11 @@ int dom_on_flow_build(void* ctx, const char* p, size_t open, size_t len, uint32_
|
|
|
820
850
|
yep_json_walk w;
|
|
821
851
|
yep_json_tok t;
|
|
822
852
|
yep_json_walk_init(&w, p, len, open, max_depth);
|
|
823
|
-
w.strict =
|
|
824
|
-
|
|
853
|
+
w.strict = 1; /* the same byte-for-byte JSON gate as the engine's
|
|
854
|
+
* pass 1: a lenient walk split number/literal keys
|
|
855
|
+
* across the fast/slow paths (whole {9->1} vs chunked
|
|
856
|
+
* {"9:1"->null}); flow_strict keeps owning the TAG
|
|
857
|
+
* route only (TODO.restructure/81) */
|
|
825
858
|
for (;;) {
|
|
826
859
|
yep_jw_status st = yep_json_walk_next(&w, &t);
|
|
827
860
|
if (st == YEP_JW_REJECT) {
|
|
@@ -976,6 +1009,23 @@ int dom_from_tape(yep_dom* d, const yeptris_json_tape* t) {
|
|
|
976
1009
|
if (d->depth == 0) {
|
|
977
1010
|
return -1;
|
|
978
1011
|
}
|
|
1012
|
+
if (d->nodes[d->stack[d->depth - 1]].kind == YEP_DOM_MAPPING &&
|
|
1013
|
+
d->map_pending_key[d->depth - 1]) {
|
|
1014
|
+
/* an odd child reached the mapping (see the event-path
|
|
1015
|
+
* MAP_END): pair it with a null instead of dropping it */
|
|
1016
|
+
yep_event nv;
|
|
1017
|
+
memset(&nv, 0, sizeof(nv));
|
|
1018
|
+
nv.type = YEP_EV_SCALAR;
|
|
1019
|
+
nv.style = YEP_STYLE_PLAIN;
|
|
1020
|
+
nv.implicit = 1;
|
|
1021
|
+
uint32_t nid = dom_new_node(d, &nv, YEP_DOM_SCALAR);
|
|
1022
|
+
if (nid == UINT32_MAX) {
|
|
1023
|
+
return -1;
|
|
1024
|
+
}
|
|
1025
|
+
dom_link(d, d->stack[d->depth - 1], d->pending_key_id[d->depth - 1]);
|
|
1026
|
+
dom_link(d, d->stack[d->depth - 1], nid);
|
|
1027
|
+
d->map_pending_key[d->depth - 1] = 0;
|
|
1028
|
+
}
|
|
979
1029
|
d->depth--;
|
|
980
1030
|
break;
|
|
981
1031
|
case YEP_T_NULL:
|