yeptris 0.6.31.2-arm-linux-eabihf → 0.6.33.1-arm-linux-eabihf

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.
@@ -36,6 +36,9 @@ int yep_engine_run(yep_engine* e, const char* buf, size_t len, const yep_sink* s
36
36
  * from the stream's '&' occurrences (every anchor definition carries
37
37
  * exactly one, so the count never undershoots). */
38
38
  void yep_engine_prepare(yep_engine* e, const yep_text_stats* st);
39
+ /* the caller already BOM-sniffed/transcoded the bytes (parse.c,
40
+ * values.c): the engine must not re-strip a content U+FEFF */
41
+ void yep_engine_front_done(yep_engine* e);
39
42
 
40
43
  /* Resumable stepping (TODO.impl/07): chunks accumulate; every feed
41
44
  * parses the complete DOCUMENTS received so far (cut at `---` / `...`
@@ -5,6 +5,7 @@
5
5
  * strtod cross-checks. */
6
6
 
7
7
  #include <errno.h>
8
+ #include <limits.h>
8
9
  #include <math.h>
9
10
  #include <stdlib.h>
10
11
  #include <string.h>
@@ -160,9 +161,20 @@ int yep_num_i64(const char* p, uint32_t len, int64_t* out) {
160
161
  }
161
162
  long long v = 0;
162
163
  while (*q >= '0' && *q <= '9') {
163
- v = v * 10 + (*q - '0');
164
+ int d = *q - '0';
165
+ /* the ERANGE law, spelled out: the plain path rejects
166
+ * unrepresentable values via strtoll, so the fold must
167
+ * too (UBSan: 2222222222222222222 * 10 overflowed, the
168
+ * nightly's round on the combined build) */
169
+ if (v > (LLONG_MAX - (long long)d) / 10) {
170
+ return 1;
171
+ }
172
+ v = v * 10 + d;
164
173
  q++;
165
174
  }
175
+ if (v > LLONG_MAX / 3600) {
176
+ return 1;
177
+ }
166
178
  v *= 3600; /* e0 weight */
167
179
  if (sign < 0)
168
180
  v = -v;
@@ -172,14 +184,27 @@ int yep_num_i64(const char* p, uint32_t len, int64_t* out) {
172
184
  long long g = 0;
173
185
  int d = 0;
174
186
  while (*q >= '0' && *q <= '9') {
175
- g = g * 10 + (*q - '0');
187
+ int dg = *q - '0';
188
+ /* the group component rides the same ERANGE law as
189
+ * the first component -- the catch-124 fix guarded
190
+ * the hours loop but left this one raw (UBSan:
191
+ * 999999999999999999 * 10, the nightly's run
192
+ * 39049702014) */
193
+ if (g > (LLONG_MAX - (long long)dg) / 10) {
194
+ return 1;
195
+ }
196
+ g = g * 10 + dg;
176
197
  q++;
177
198
  d++;
178
199
  }
179
200
  if (d == 0) {
180
201
  return 1;
181
202
  }
182
- v += g * (groups == 0 ? 60 : 1); /* e1 then e2 */
203
+ long long w = groups == 0 ? 60 : 1; /* e1 then e2 */
204
+ if (g > LLONG_MAX / w || v > LLONG_MAX - g * w) {
205
+ return 1;
206
+ }
207
+ v += g * w;
183
208
  groups++;
184
209
  }
185
210
  if (groups == 0 || *q != '\0') {
@@ -19,6 +19,18 @@
19
19
  extern "C" {
20
20
  #endif
21
21
 
22
+ /* libyaml's simple-key budget counts characters, not bytes: a
23
+ * multibyte code point costs one (the round-108 fuzz shrink carries a
24
+ * 2-byte char at byte delta 1025). Shared by the engine scanners, the
25
+ * DOM JSON builder, and the tape key checker. */
26
+ static inline int yep_simple_key_chars(const char* p, size_t n) {
27
+ int chars = 0;
28
+ for (size_t i = 0; i < n; i++) {
29
+ chars += ((((unsigned char)p[i]) & 0xC0) != 0x80);
30
+ }
31
+ return chars;
32
+ }
33
+
22
34
  /* Finishes a double-quoted content span: escape sequences (\n \t \\ \" \x
23
35
  * \u \U …), backslash-at-EOL continuation, and multi-line folding.
24
36
  * Returns a pool-owned buffer of out_len bytes. */
@@ -156,10 +156,14 @@ YEPTRIS_API YeptrisDocument yeptris_parse_json(const char* buf, size_t len, Yept
156
156
  st = YEPTRIS_ERROR_MEMORY;
157
157
  goto jfail;
158
158
  }
159
- if (yep_tape_walk_lenient_fused(buf, len, off, t, 1, 1, 0) == YEPTRIS_OK) {
159
+ if (yep_tape_walk_lenient_fused(buf, len, off, t, 1, 1, 0) == YEPTRIS_OK &&
160
+ !yep_tape_long_simple_key(t)) {
160
161
  /* strict_nums=1: the arms validated the numbers at
161
162
  * record time — the settle pass is gone (the profile's
162
- * 10% lane; the spans were cache-warm in the walk) */
163
+ * 10% lane; the spans were cache-warm in the walk).
164
+ * The simple-key law: a >1024 flow key cannot
165
+ * re-serialize — the engine's clean error decides (run
166
+ * 38143486175) */
163
167
  YeptrisDocument h = yep_json_doc_wrap(NULL, buf, len, sys, status);
164
168
  if (h != NULL) {
165
169
  ((yeptris_document*)h)->lazy_tape = t;
@@ -366,8 +370,11 @@ engine_enter:
366
370
  * entirely — no anchors exist (the profile's 4.4% memchr
367
371
  * lane on the json corpora). */
368
372
  yep_text_stats amp_only = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
369
- const char* q = data;
370
- const char* end = data + data_len;
373
+ /* (NULL, 0) passes the arg guard: NULL + 0 is UB, not an
374
+ * empty range (UBSan, the first nightly under the combined
375
+ * fuzz build) */
376
+ const char* q = data == NULL ? "" : data;
377
+ const char* end = q + data_len;
371
378
  if (q != NULL) {
372
379
  while ((q = memchr(q, '&', (size_t)(end - q))) != NULL) {
373
380
  amp_only.amp++;
@@ -421,6 +428,7 @@ engine_enter:
421
428
  if (have_tape) {
422
429
  ytap_reset(&yt);
423
430
  }
431
+ yep_engine_front_done(eng);
424
432
  rc = yep_engine_run(eng, data, data_len, &sink);
425
433
  }
426
434
  if (rc != 0) {
@@ -29,6 +29,15 @@ void yep_scan_advance_line(const char* p, size_t* from, size_t to, uint32_t* lin
29
29
  if (p[i] == '\n') {
30
30
  (*line)++;
31
31
  *line_start = i + 1;
32
+ } else if (p[i] == '\r' && i + 1 < to && p[i + 1] != '\n') {
33
+ /* a lone \r is a break too (the engine's break rule):
34
+ * the JSON fast path's walk missed it and marked every
35
+ * event after the \r one line low while the general
36
+ * kernel (which walks \r) marked them correctly — whole
37
+ * vs chunked diverged on `\r`-containing JSON-class flow
38
+ * (the nightly's run 38549939326) */
39
+ (*line)++;
40
+ *line_start = i + 1;
32
41
  }
33
42
  }
34
43
  *from = to;
@@ -151,6 +160,39 @@ size_t yep_scan_prop_end(const char* p, size_t len, size_t pos) {
151
160
  return pos;
152
161
  }
153
162
 
163
+ int yep_scan_tag_char(unsigned char c) {
164
+ if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
165
+ return 1;
166
+ }
167
+ if (c >= 0x80) {
168
+ return 1; /* UTF-8 suffix bytes pass through */
169
+ }
170
+ switch (c) {
171
+ case '-':
172
+ case '_':
173
+ case ';':
174
+ case '/':
175
+ case '?':
176
+ case ':':
177
+ case '@':
178
+ case '&':
179
+ case '=':
180
+ case '+':
181
+ case '$':
182
+ case '.':
183
+ case '!':
184
+ case '~':
185
+ case '*':
186
+ case '\'':
187
+ case '(':
188
+ case ')':
189
+ case '%':
190
+ return 1;
191
+ default:
192
+ return 0;
193
+ }
194
+ }
195
+
154
196
  /* ---- line-shape classification (TODO.restructure/49) ---- */
155
197
 
156
198
  /* blank/EOL/EOF directly after p[at] (the dash / explicit-key rule). */
@@ -98,6 +98,13 @@ int yep_scan_prop_char(unsigned char c);
98
98
  /* First offset at/after pos that cannot continue an anchor/alias name. */
99
99
  size_t yep_scan_prop_end(const char* p, size_t len, size_t pos);
100
100
 
101
+ /* Tag-shorthand suffix byte: libyaml's URI set (";/?:@&=+$,_.!~*'()%"
102
+ * plus alphanumerics; flow indicators and ',' terminate instead).
103
+ * Stray non-set bytes stay visible as the props follower, which
104
+ * e_props rejects ("did not find expected whitespace or line break
105
+ * while scanning a tag"). */
106
+ int yep_scan_tag_char(unsigned char c);
107
+
101
108
  /* Scans a plain scalar starting at pos (must be content, not a comment).
102
109
  * flow != 0 adds flow stop characters; in flow, ':' terminates when
103
110
  * followed by blank/EOL/flow indicator. Leading whitespace is NOT
@@ -34,6 +34,7 @@
34
34
  #include "common/simd_text.h"
35
35
  #include "encoding/encoding.h"
36
36
  #include "memory/allocator.h"
37
+ #include "parse/scalars.h"
37
38
  #include "scan/json.h"
38
39
 
39
40
  typedef struct {
@@ -685,6 +686,67 @@ static TAPE_NUM_OK_INLINE int tape_num_ok(const char* p, size_t len) {
685
686
  return k == len;
686
687
  }
687
688
 
689
+ /* The >1024 simple-key law for the LENIENT walk's tape — the same
690
+ * verdict yt_flow_scan reads for the record walk: a STR at a map's
691
+ * key slot with a raw span past the limit (the law counts the RAW
692
+ * token, so a quoted key's two quotes add back). The DOM route must
693
+ * reject it BEFORE wrapping (strict JSON itself has no limit — the
694
+ * tape API keeps accepting; only the YAML serialization cannot carry
695
+ * a long simple key, so the fast parse defers to the engine's clean
696
+ * error — the nightly's run 38143486175). */
697
+ int yep_tape_long_simple_key(const yeptris_json_tape* s) {
698
+ struct {
699
+ uint8_t map;
700
+ uint8_t key_next;
701
+ } st[257];
702
+ int d = 0;
703
+ st[0].map = 0;
704
+ st[0].key_next = 0;
705
+ for (uint32_t i = 1; i < s->count; i++) { /* recs[0] is the DOC */
706
+ uint64_t r = s->recs[i];
707
+ uint8_t kind = (uint8_t)(r & 0xFFu);
708
+ if (kind == YEP_T_MAP_OPEN || kind == YEP_T_SEQ_OPEN) {
709
+ if (d >= 256) {
710
+ return 1; /* deeper than the scratch stack: over by
711
+ * definition, the engine's depth law decides */
712
+ }
713
+ /* the container consumes the parent frame's pending slot;
714
+ * after it closes the OTHER slot is next (yt_flow_scan's
715
+ * round-108 lesson: a pinned key_next hid every key that
716
+ * followed a container value) */
717
+ if (d > 0 && st[d].map) {
718
+ st[d].key_next = (uint8_t)!st[d].key_next;
719
+ }
720
+ d++;
721
+ st[d].map = (uint8_t)(kind == YEP_T_MAP_OPEN);
722
+ st[d].key_next = 0;
723
+ continue;
724
+ }
725
+ if (kind == YEP_T_CLOSE) {
726
+ if (d > 0) {
727
+ d--;
728
+ }
729
+ continue;
730
+ }
731
+ if (st[d].map && st[d].key_next == 0) {
732
+ uint32_t mlen = (uint32_t)(r >> 8) & 0xFFFFFFu;
733
+ if (kind == YEP_T_STR && mlen + 2u > 1024u &&
734
+ yep_simple_key_chars((const char*)s->_src + (size_t)((uint32_t)(r >> 32)), mlen) +
735
+ 2 >
736
+ 1024) {
737
+ /* the budget counts characters, not bytes (the round-108
738
+ * shrink: a 2-byte code point at byte delta 1025 is
739
+ * legal); _src rides the walk that produced the tape */
740
+ return 1;
741
+ }
742
+ st[d].key_next = 1;
743
+ } else if (st[d].map) {
744
+ st[d].key_next = 0;
745
+ }
746
+ }
747
+ return 0;
748
+ }
749
+
688
750
  YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
689
751
  yeptris_json_tape* t, int strict_nums, int clean_only,
690
752
  int reuse_block) {
@@ -1194,6 +1256,17 @@ ldone:
1194
1256
  goto lreject;
1195
1257
  }
1196
1258
  }
1259
+ /* a col-0 tab-led continuation line inside the span is the suite's
1260
+ * Y79Y#4 error: defer to the engine, whose e_flow_ws owns the law
1261
+ * -- the walk's whitespace loops treat the tab as separation, which
1262
+ * accepted exactly the span the marshal drain's engine rejected,
1263
+ * splitting parseable from marshalable (the nightly's round 120) */
1264
+ for (size_t k = open + 1; k < len; k++) {
1265
+ if (p[k] == '\n' && k + 1 < len && p[k + 1] == '\t') {
1266
+ yeptris_tape_free(t);
1267
+ return YEPTRIS_ERROR_PARSE;
1268
+ }
1269
+ }
1197
1270
  t->count = count;
1198
1271
  t->_src = p;
1199
1272
  t->_srclen = len;
@@ -2013,7 +2086,21 @@ YEPTRIS_API YeptrisStatus yeptris_parse_json_tape_lenient(const char* source, si
2013
2086
  * 1.65x, json-users 2.2x — the ledger's #85 scoping), and
2014
2087
  * this exact call already carried the route's verdicts as the
2015
2088
  * escape/C0 fallback, so acceptance is unchanged. */
2016
- return yep_tape_walk_lenient_fused(source, len, at, tape, 0, 0, 0);
2089
+ int rc = yep_tape_walk_lenient_fused(source, len, at, tape, 0, 0, 0);
2090
+ if (rc != YEPTRIS_OK) {
2091
+ return rc;
2092
+ }
2093
+ /* the YAML simple-key law rides the fused route too: a key
2094
+ * whose raw span crosses 1024 defers to the engine's clean
2095
+ * error — the JSON-class walk has no YAML laws, and the other
2096
+ * routes (engine_run, the marshal drains) reject the same
2097
+ * span, so acceptance here broke whole/chunked marshal parity
2098
+ * (the nightly's round 108) */
2099
+ if (yep_tape_long_simple_key(tape)) {
2100
+ yep_error_set(yep_error_tls(), YEP_ERR_KEY_TOO_LONG, 1, (uint32_t)(at + 1), at, NULL);
2101
+ return YEPTRIS_ERROR_PARSE;
2102
+ }
2103
+ return YEPTRIS_OK;
2017
2104
  }
2018
2105
 
2019
2106
  /* scalar root: one record, same deferred split for numbers */
@@ -14,6 +14,8 @@
14
14
  #include <yeptris/tape.h>
15
15
 
16
16
  /* tape.c (was tape_walk_lnt_fused) */
17
+ int yep_tape_long_simple_key(const yeptris_json_tape* t);
18
+
17
19
  YeptrisStatus yep_tape_walk_lenient_fused(const char* p, size_t len, size_t open,
18
20
  yeptris_json_tape* t, int strict_nums, int clean_only,
19
21
  int reuse_block);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.31.2
4
+ version: 0.6.33.1
5
5
  platform: arm-linux-eabihf
6
6
  authors:
7
7
  - Ribose Inc.