yeptris 0.6.51.0-arm-linux-eabihf → 0.6.53.0-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.
@@ -24,6 +24,7 @@
24
24
 
25
25
  #include "style.h"
26
26
 
27
+ #include <math.h>
27
28
  #include <stdio.h>
28
29
  #include <stdlib.h>
29
30
 
@@ -95,6 +96,40 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
95
96
  w->len += n;
96
97
  return;
97
98
  }
99
+ if (w->sink != NULL && !w->grow && !w->bounded && w->cap > 0 &&
100
+ (size_t)n > w->cap - w->len - 1) {
101
+ /* the fixed-window streaming route cannot hold this put even
102
+ * after the watermark flush: CHUNK it — window-sized pieces
103
+ * with a flush between each. Append-only output makes any cut
104
+ * a safe cut; the sink sees more, smaller chunks. Memory stays
105
+ * bounded by the window (the route's whole point) — the old
106
+ * unconditional memcpy wrote straight past it for any scalar
107
+ * larger than the remaining room (the 2026-10-31 audit's
108
+ * streaming-overflow finding). */
109
+ size_t off = 0;
110
+ while (off < (size_t)n) {
111
+ if (w->sink_aborted) {
112
+ w->len += (size_t)n - off; /* keep counting: the caller fails */
113
+ return;
114
+ }
115
+ size_t room = w->cap - w->len - 1;
116
+ if (room > (size_t)n - off) {
117
+ room = (size_t)n - off;
118
+ }
119
+ memcpy(w->p + w->len, p + off, room);
120
+ w->len += room;
121
+ off += room;
122
+ if (off < (size_t)n) {
123
+ if (w->sink(w->sink_ctx, w->p, w->len) != 0) {
124
+ w->sink_aborted = 1;
125
+ } else {
126
+ w->flushed += w->len;
127
+ w->len = 0;
128
+ }
129
+ }
130
+ }
131
+ return;
132
+ }
98
133
  /* the +1 reserves the terminator slot: the serializers write the
99
134
  * NUL at p[len] after the run, and a write landing exactly on cap
100
135
  * (len + n == cap) put that NUL one byte past the buffer — the
@@ -487,10 +522,11 @@ enum { YEP_SC_NOTHING = 0, YEP_SC_PLAIN, YEP_SC_SQ, YEP_SC_DQ, YEP_SC_LITERAL, Y
487
522
  * a key whose content demotes to DQ re-parses as a QUOTED key and must
488
523
  * ride simple, or round two's render differs (the nightly's round 118). */
489
524
  static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key, uint8_t sty_in,
490
- int has_tag, int no_exotic) {
491
- /* the parse-time fact bit (#352 lane 2): a dnode verified clean of
492
- * the dq-forced set skips the rescan — 0 (mutation-built or JSON
493
- * classes) keeps the scan */
525
+ int has_tag, int no_exotic, int plain_fact) {
526
+ /* the parse-time fact bits (#352 lanes 2-3): a dnode verified clean
527
+ * of the dq-forced set skips that rescan, and a verified plain-safe
528
+ * span skips the safety walk — 0 (mutation-built or JSON classes)
529
+ * keeps the derive */
494
530
  if (!no_exotic && yep_scan_dq_forced(p, len)) {
495
531
  return YEP_SC_DQ;
496
532
  }
@@ -535,7 +571,10 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
535
571
  int plain_ok = 0;
536
572
  int plain_checked = 0;
537
573
  if ((sty_in == 1) && len > 0) {
538
- plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
574
+ plain_ok = plain_fact;
575
+ if (!plain_ok) {
576
+ plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
577
+ }
539
578
  plain_checked = 1;
540
579
  if (plain_ok) {
541
580
  return YEP_SC_PLAIN; /* plain-safe implies no breaks: memchr skipped */
@@ -594,7 +633,10 @@ static uint8_t sc_route(yep_writer* w, const char* p, uint32_t len, int as_key,
594
633
  return as_key ? YEP_SC_DQ : YEP_SC_NOTHING;
595
634
  }
596
635
  if (!plain_checked) {
597
- plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
636
+ plain_ok = plain_fact;
637
+ if (!plain_ok) {
638
+ plain_ok = as_key ? yep_style_plain_key_safe(p, len) : yep_style_plain_safe(p, len);
639
+ }
598
640
  plain_checked = 1;
599
641
  }
600
642
  if (plain_ok) {
@@ -635,14 +677,15 @@ static void emit_scalar(yep_writer* w, const yep_dnode* n, int parent_col, int a
635
677
  * memoized long scalars, in visit order either way. */
636
678
  if (w->sc_dec != NULL && len >= YEP_SC_MEMO_MIN) {
637
679
  if (w->dry) {
638
- route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0, n->no_exotic);
680
+ route =
681
+ sc_route(w, p, len, as_key, n->style, n->tag.len > 0, n->no_exotic, n->plain_ok);
639
682
  w->sc_dec[w->sc_i] = route;
640
683
  } else {
641
684
  route = w->sc_dec[w->sc_i];
642
685
  }
643
686
  w->sc_i++;
644
687
  } else {
645
- route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0, n->no_exotic);
688
+ route = sc_route(w, p, len, as_key, n->style, n->tag.len > 0, n->no_exotic, n->plain_ok);
646
689
  }
647
690
  switch (route) {
648
691
  case YEP_SC_NOTHING:
@@ -762,8 +805,14 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
762
805
  if (anchor_name_unspellable(w, n)) {
763
806
  return;
764
807
  }
765
- wr_byte(w, '&');
766
808
  yep_view adec = wv(w, n->anchor);
809
+ if (w->json) {
810
+ /* JSON spells no anchors — but a name the emitter could never
811
+ * spell still fails the entry (the "{k: &a?b v}" law) */
812
+ wr_anchor_check(w, &adec);
813
+ return;
814
+ }
815
+ wr_byte(w, '&');
767
816
  if (w->canonical) {
768
817
  uint32_t idx = yep_nametab_get(&em->canon_names, adec);
769
818
  if (idx == YEP_NAMETAB_NIL) {
@@ -782,6 +831,12 @@ static void emit_anchor_ex(yep_emitter* em, const yep_dnode* n) {
782
831
 
783
832
  static void emit_alias_ex(yep_emitter* em, const yep_dnode* n) {
784
833
  yep_writer* w = &em->w;
834
+ if (w->json) {
835
+ /* JSON has no alias references: the projection writes null
836
+ * (bounded — dereferencing would loop on cycles) */
837
+ wr_put(w, "null", 4);
838
+ return;
839
+ }
785
840
  yep_view name_v = wv(w, n->value);
786
841
  if (w->in_flow && !w->canonical) {
787
842
  /* the alias twin of wr_anchor_check: a flow alias is spelled
@@ -916,6 +971,9 @@ static void emit_tag(yep_writer* w, const yep_dnode* n) {
916
971
  if (n->tag.len == 0) {
917
972
  return;
918
973
  }
974
+ if (w->json) {
975
+ return; /* JSON spells no tags (the ts probe: "k: !" -> {"k": ! ""}) */
976
+ }
919
977
  yep_view tdec = wv(w, n->tag);
920
978
  wr_tag_token(w, &tdec);
921
979
  wr_byte(w, ' ');
@@ -925,15 +983,18 @@ static void emit_tag(yep_writer* w, const yep_dnode* n) {
925
983
  * "key:"/"-" — properties may not precede a dash entry directly (the
926
984
  * suite's SY6V rule). */
927
985
  static void emit_props_tail(yep_writer* w, const yep_dnode* n) {
986
+ if (n->anchor.len > 0 && anchor_name_unspellable(w, n)) {
987
+ return;
988
+ }
989
+ if (w->json) {
990
+ return; /* JSON spells no properties */
991
+ }
928
992
  if (n->tag.len > 0) {
929
993
  wr_byte(w, ' ');
930
994
  yep_view tdec2 = wv(w, n->tag);
931
995
  wr_tag_token(w, &tdec2);
932
996
  }
933
997
  if (n->anchor.len > 0) {
934
- if (anchor_name_unspellable(w, n)) {
935
- return;
936
- }
937
998
  wr_byte(w, ' ');
938
999
  yep_view adec2 = wv(w, n->anchor);
939
1000
  wr_anchor_check(w, &adec2);
@@ -1120,42 +1181,57 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1120
1181
  vn0->count == 0) {
1121
1182
  yep_view kv = wv(w, kn0->value);
1122
1183
  yep_view vv = wv(w, vn0->value);
1123
- if (kv.len > 0 && !key_written_over_limit(w, kn0) &&
1124
- yep_style_plain_key_safe((const char*)kv.p, kv.len) &&
1125
- (vv.len == 0 || yep_style_plain_safe((const char*)vv.p, vv.len))) {
1184
+ uint32_t f_ind = 0, f_nl = 0, f_total = 0;
1185
+ int fused = kv.len > 0 && !key_written_over_limit(w, kn0) && kn0->plain_ok &&
1186
+ (vv.len == 0 || vn0->plain_ok);
1187
+ if (fused) {
1126
1188
  /* fused member write: plain-safe spans carry no breaks,
1127
1189
  * so the col scan is arithmetic and one reservation
1128
1190
  * covers the whole member (line + key + ':' + value) */
1129
- uint32_t ind = pair > 0 ? (uint32_t)content_col : 0u;
1130
- uint32_t nl = (pair > 0 && w->last != '\n') ? 1u : 0u;
1131
- uint32_t total = nl + ind + kv.len + 1u + (vv.len > 0 ? vv.len + 1u : 0u);
1191
+ f_ind = pair > 0 ? (uint32_t)content_col : 0u;
1192
+ f_nl = (pair > 0 && w->last != '\n') ? 1u : 0u;
1193
+ f_total = f_nl + f_ind + kv.len + 1u + (vv.len > 0 ? vv.len + 1u : 0u);
1194
+ /* the fixed-window streaming route: the fused write
1195
+ * bypasses wr_put (whose chunked path bounds every
1196
+ * put), and this block ends in continue — so a member
1197
+ * that does not fit the window must DECLINE THE WHOLE
1198
+ * ARM to reach the general body, which emits the
1199
+ * identical bytes through wr_put. cap==0 routes are the
1200
+ * old trusted-count forms: the writer cannot reason
1201
+ * about their capacity, and they keep the arm. */
1202
+ if (!w->dry && !w->grow && w->cap != 0 && w->len + (size_t)f_total + 1 > w->cap) {
1203
+ fused = 0;
1204
+ }
1205
+ }
1206
+ if (fused) {
1132
1207
  w->last = vv.len > 0 ? (char)vv.p[vv.len - 1] : ':';
1133
- if (nl) {
1134
- w->col = (int)(total - 1u); /* the break resets, then the line */
1208
+ if (f_nl) {
1209
+ w->col = (int)(f_total - 1u); /* the break resets, then the line */
1135
1210
  } else {
1136
- w->col += (int)total;
1211
+ w->col += (int)f_total;
1137
1212
  }
1138
1213
  if (w->dry) {
1139
- w->len += total;
1214
+ w->len += f_total;
1140
1215
  } else {
1141
- if (w->grow && w->len + total + 1 > w->cap) {
1216
+ if (w->grow && w->len + f_total + 1 > w->cap) {
1142
1217
  if (w->bounded) {
1143
1218
  w->oom = 1; /* keep counting: the trusted caller reads the size */
1144
1219
  } else {
1145
- wr_grow(w, total);
1220
+ wr_grow(w, f_total);
1146
1221
  }
1147
1222
  }
1148
1223
  if (w->oom) {
1149
- w->len += total; /* keep counting: the caller fails on oom */
1224
+ w->len += f_total; /* keep counting: the caller fails on oom */
1150
1225
  } else {
1151
1226
  /* the fused write bypasses wr_put/wr_byte: keep
1152
1227
  * the reservation honest (the #739 lane-1 law) */
1153
- w->reserve = (size_t)total <= w->reserve ? w->reserve - (size_t)total : 0;
1228
+ w->reserve =
1229
+ (size_t)f_total <= w->reserve ? w->reserve - (size_t)f_total : 0;
1154
1230
  char* q = w->p + w->len;
1155
- if (nl) {
1231
+ if (f_nl) {
1156
1232
  *q++ = '\n';
1157
1233
  }
1158
- for (uint32_t i = 0; i < ind; i++) {
1234
+ for (uint32_t i = 0; i < f_ind; i++) {
1159
1235
  *q++ = ' ';
1160
1236
  }
1161
1237
  memcpy(q, kv.p, kv.len);
@@ -1165,7 +1241,7 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1165
1241
  *q++ = ' ';
1166
1242
  memcpy(q, vv.p, vv.len);
1167
1243
  }
1168
- w->len += total;
1244
+ w->len += f_total;
1169
1245
  wr_maybe_flush(w);
1170
1246
  }
1171
1247
  }
@@ -1445,9 +1521,10 @@ static void emit_json_flow(yep_emitter* em, uint32_t id) {
1445
1521
  }
1446
1522
  if (map) {
1447
1523
  /* the one generic-path rule JSON keeps: a non-scalar key
1448
- * (or one carrying props) rides the explicit "? key : "
1449
- * form — same bytes emit_flow writes */
1450
- if (cn->anchor.len > 0 || cn->tag.len > 0 || cn->kind != 0) {
1524
+ * rides the explicit "? key : " form — same bytes emit_flow
1525
+ * writes. (Propertied scalar keys used to join it; JSON
1526
+ * drops properties, so they ride the plain quoted form.) */
1527
+ if (cn->kind != 0) {
1451
1528
  wr_put(w, "? ", 2);
1452
1529
  emit_json_node(em, child);
1453
1530
  wr_put(w, " : ", 3);
@@ -1546,6 +1623,10 @@ typedef struct {
1546
1623
  * 'I'-wrapped construct registers ONCE (at the 'I');
1547
1624
  * its immediate inner must not add a second entry or
1548
1625
  * every later '@' link shifts */
1626
+ int depth; /* mj_value recursion guard — the reader walks hostile
1627
+ * bytes, not Ruby's heap: nesting is unbounded in the
1628
+ * input, the stack is not (the parser's own
1629
+ * max_depth=1000 is the same law) */
1549
1630
  } mj_ctx;
1550
1631
 
1551
1632
  static int mj_fail(mj_ctx* c) {
@@ -1672,7 +1753,23 @@ static int mj_ivar_pairs(mj_ctx* c) {
1672
1753
  }
1673
1754
 
1674
1755
  /* stream-position-only walk: consumes one value, emits nothing */
1756
+
1757
+ #define MJ_DEPTH_MAX 1000
1758
+
1759
+ /* the discard twin rides the same budget (its 'I'/'['/'{' recursion is
1760
+ * just as hostile-byte reachable) */
1761
+ static int mj_discard_value_inner(mj_ctx* c);
1762
+
1675
1763
  static int mj_discard_value(mj_ctx* c) {
1764
+ if (c->depth >= MJ_DEPTH_MAX)
1765
+ return mj_fail(c);
1766
+ c->depth++;
1767
+ int rc = mj_discard_value_inner(c);
1768
+ c->depth--;
1769
+ return rc;
1770
+ }
1771
+
1772
+ static int mj_discard_value_inner(mj_ctx* c) {
1676
1773
  unsigned char t;
1677
1774
  int rc = mj_byte(c, &t);
1678
1775
  if (rc != 0)
@@ -1771,7 +1868,20 @@ static int mj_emit_memo(mj_ctx* c, const mj_obj* o) {
1771
1868
  return 0;
1772
1869
  }
1773
1870
 
1871
+ static int mj_value_inner(mj_ctx* c);
1872
+
1873
+ /* the wrapper owns the recursion budget: hostile bytes can nest arrays
1874
+ * without bound, the stack cannot (the parser's max_depth=1000 law) */
1774
1875
  static int mj_value(mj_ctx* c) {
1876
+ if (c->depth >= MJ_DEPTH_MAX)
1877
+ return mj_fail(c);
1878
+ c->depth++;
1879
+ int rc = mj_value_inner(c);
1880
+ c->depth--;
1881
+ return rc;
1882
+ }
1883
+
1884
+ static int mj_value_inner(mj_ctx* c) {
1775
1885
  unsigned char t;
1776
1886
  size_t tpos = c->s.at;
1777
1887
  int reg_idx = -1;
@@ -1838,7 +1948,9 @@ static int mj_value(mj_ctx* c) {
1838
1948
  unsigned char lo, hi;
1839
1949
  if (mj_byte(c, &lo) != 0 || mj_byte(c, &hi) != 0)
1840
1950
  return -1;
1841
- v |= (int64_t)(lo | ((uint64_t)hi << 8)) << (16 * i);
1951
+ /* shift in the unsigned domain: the signed form put
1952
+ * 0xFFFF into the sign bit at limb 3 (UB, UBSan-flagged) */
1953
+ v |= (int64_t)((uint64_t)(lo | ((uint64_t)hi << 8)) << (16 * i));
1842
1954
  }
1843
1955
  if (sign == 0x2d)
1844
1956
  v = -v;
@@ -1858,9 +1970,11 @@ static int mj_value(mj_ctx* c) {
1858
1970
  memcpy(tmp, c->s.p + c->s.at, tl);
1859
1971
  tmp[tl] = '\0';
1860
1972
  c->s.at += (size_t)len;
1861
- if (strcmp(tmp, "inf") == 0 || strcmp(tmp, "-inf") == 0 || strcmp(tmp, "nan") == 0)
1862
- return mj_fail(c); /* JSON has no inf/nan spelling */
1863
1973
  double d = strtod(tmp, NULL);
1974
+ if (!isfinite(d))
1975
+ return mj_fail(c); /* JSON has no inf/nan spelling — and a
1976
+ * strtod OVERFLOW ("1e999") produces
1977
+ * one from text that never spells it */
1864
1978
  char buf[40];
1865
1979
  int wl = yep_d2s_json(d, buf);
1866
1980
  wr_put(c->w, buf, (uint32_t)wl);
@@ -291,8 +291,13 @@ static int psych_int_text(const char* p, uint32_t len) {
291
291
  }
292
292
 
293
293
  /* decimal text -> bignum limbs (base 2^16 LE), then Marshal's 'l':
294
- * sign + w_long(limb count) + limbs. 128 limbs = 2048 bits covers
295
- * ~616 decimal digits; longer texts stay Strings (pathological). */
294
+ * sign + w_long(limb count) + limbs. The gate (marshal_node: text
295
+ * under 1300 chars) bounds the digits at 1299 -> ceil(1299*log2(10)/16)
296
+ * = 270 limbs; the arrays are sized from that bound so no carry is
297
+ * ever dropped (the old 128-limb cap silently truncated 617..1299-digit
298
+ * values into wrong numbers). */
299
+ #define E_BIGNUM_LIMBS_MAX 272
300
+
296
301
  static void e_bignum_text(E* e, const char* p, uint32_t len) {
297
302
  int neg = 0;
298
303
  if (p[0] == '-' || p[0] == '+') {
@@ -300,7 +305,7 @@ static void e_bignum_text(E* e, const char* p, uint32_t len) {
300
305
  p++;
301
306
  len--;
302
307
  }
303
- uint16_t limbs[128];
308
+ uint16_t limbs[E_BIGNUM_LIMBS_MAX];
304
309
  memset(limbs, 0, sizeof(limbs));
305
310
  int nl = 1;
306
311
  for (uint32_t i = 0; i < len; i++) {
@@ -313,7 +318,7 @@ static void e_bignum_text(E* e, const char* p, uint32_t len) {
313
318
  limbs[j] = (uint16_t)(t & 0xffffu);
314
319
  carry = t >> 16;
315
320
  }
316
- while (carry != 0 && nl < 128) {
321
+ while (carry != 0 && nl < E_BIGNUM_LIMBS_MAX) {
317
322
  limbs[nl++] = (uint16_t)(carry & 0xffffu);
318
323
  carry >>= 16;
319
324
  }
@@ -324,7 +329,7 @@ static void e_bignum_text(E* e, const char* p, uint32_t len) {
324
329
  e_byte(e, 'l');
325
330
  e_byte(e, neg ? '-' : '+');
326
331
  e_long(e, nl);
327
- unsigned char b[256];
332
+ unsigned char b[E_BIGNUM_LIMBS_MAX * 2];
328
333
  for (int i = 0; i < nl; i++) {
329
334
  b[2 * i] = (unsigned char)(limbs[i] & 0xff);
330
335
  b[2 * i + 1] = (unsigned char)(limbs[i] >> 8);