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.
@@ -20,6 +20,7 @@
20
20
  #include "dom/dom.h"
21
21
  #include "memory/allocator.h"
22
22
  #include "memory/pool.h"
23
+ #include "parse/scalars.h"
23
24
  #include "resolve/resolver.h"
24
25
  #include "scan/json.h"
25
26
  #include "scan/scan.h"
@@ -355,6 +356,14 @@ static uint32_t yt_flow_scan(const yeptris_json_tape* s, int* long_key) {
355
356
  return 0xFFFFu; /* deeper than the scratch stack: the
356
357
  * general kernel's depth path decides */
357
358
  }
359
+ /* the container consumes the parent frame's pending slot
360
+ * (key or value); after it closes the OTHER slot is next —
361
+ * a container value left key_next pinned at 1, and every
362
+ * key after a container value skipped the law (the
363
+ * nightly's round-108 shape: {"": [...], "giant": x}) */
364
+ if (d > 0 && st[d].map) {
365
+ st[d].key_next = (uint8_t)!st[d].key_next;
366
+ }
358
367
  d++;
359
368
  if ((uint32_t)d > maxd) {
360
369
  maxd = (uint32_t)d;
@@ -371,7 +380,12 @@ static uint32_t yt_flow_scan(const yeptris_json_tape* s, int* long_key) {
371
380
  }
372
381
  if (st[d].map && st[d].key_next == 0) {
373
382
  uint32_t mlen = (uint32_t)(r >> 8) & 0xFFFFFFu;
374
- if (kind == YEP_T_STR && mlen + 2u > 1024u) {
383
+ if (kind == YEP_T_STR && mlen + 2u > 1024u &&
384
+ yep_simple_key_chars((const char*)s->_src + (size_t)((uint32_t)(r >> 32)), mlen) +
385
+ 2 >
386
+ 1024) {
387
+ /* the budget counts characters (the round-108 shrink:
388
+ * a 2-byte code point at byte delta 1025 is legal) */
375
389
  *long_key = 1;
376
390
  }
377
391
  st[d].key_next = 1;
@@ -429,7 +443,9 @@ static int yt_flow_classify_record(yep_ytape* t, const char* p, size_t open, siz
429
443
  yep_json_walk w;
430
444
  yep_json_tok tok;
431
445
  yep_json_walk_init(&w, p, len, open, max_depth);
432
- w.strict = t->flow_strict;
446
+ w.strict = 1; /* mirrors the engine pass-1 / dom builder gates:
447
+ * reject anything non-JSON so the span falls to
448
+ * the general kernel (whole/chunked agreement) */
433
449
  int exhausted = 0;
434
450
  for (;;) {
435
451
  if (w.i - open > 256) {
@@ -1118,10 +1134,21 @@ static size_t yt_flat_run(yep_ytape* t, const char* p, size_t pos, size_t len, u
1118
1134
  if (col + 1 != run_cc) {
1119
1135
  return pos; /* off-column: the pop/nest logic owns it */
1120
1136
  }
1137
+ /* document markers are the engine's alone — the "..."+content
1138
+ * error law lives there (the nightly's marshal crasher) */
1139
+ if (le - li >= 3 && (memcmp(p + li, "...", 3) == 0 || memcmp(p + li, "---", 3) == 0)) {
1140
+ size_t mk2 = li + 3;
1141
+ if (mk2 >= le || p[mk2] == ' ' || p[mk2] == '\t') {
1142
+ return pos;
1143
+ }
1144
+ }
1121
1145
  unsigned char c = (unsigned char)p[li];
1122
1146
  if (c == '\t' || c == '-' || c == '&' || c == '!' || c == '*' || c == '\'' || c == '"' ||
1123
- c == '[' || c == '{' || c == '|' || c == '>' || c == '%' || c == '?') {
1124
- return pos; /* indicators, tab-led: the full arms */
1147
+ c == '[' || c == '{' || c == '|' || c == '>' || c == '%' || c == '?' ||
1148
+ !yep_plain_first_ok(c)) {
1149
+ return pos; /* indicators, tab-led, and the plain-first law
1150
+ (a ']' key rode the flat-run — the nightly's
1151
+ marshal crasher "<: .ba\n]ge: 21"): full arms */
1125
1152
  }
1126
1153
  if (!stop_set || p[ls] != ':' || (ls + 1 < le && p[ls + 1] != ' ' && p[ls + 1] != '\t')) {
1127
1154
  return pos; /* not a plain key: value line */
@@ -1184,6 +1211,11 @@ static size_t yt_flat_run(yep_ytape* t, const char* p, size_t pos, size_t len, u
1184
1211
  (vt + 1 >= le || p[vt + 1] == ' ' || p[vt + 1] == '\t' || p[vt + 1] == '#'))) {
1185
1212
  return pos; /* block/flow/anchor values: the full arms */
1186
1213
  }
1214
+ if (!yep_plain_first_ok(vc)) {
1215
+ return pos; /* ']' '}' ',' '@' '`': no plain value can start
1216
+ * there (the nightly's "k1: --a &p{\n0: ]" marshal
1217
+ * crasher) — the engine errors cleanly */
1218
+ }
1187
1219
  /* a terminating ':' inside the value is the mapping-values error */
1188
1220
  {
1189
1221
  const char* h = (const char*)memchr(p + vt, ':', le - vt);
@@ -1225,6 +1257,15 @@ static int yt_f_run(yt_fused* F) {
1225
1257
  st[0].started = 1; /* the root's start event defers to its first pair */
1226
1258
  int root_open = 0;
1227
1259
  int root_evented = 0;
1260
+ uint32_t root_pair_col = 0; /* the root map's first pair line's column
1261
+ * (0 = none seen): the second-root dedent
1262
+ * law reads this, NOT content_col — the
1263
+ * pending arm never set content_col (" k:
1264
+ * &a", the nightly's run 39064966578,
1265
+ * issue #630) and arming content_col here
1266
+ * instead broke the " k: \n j: w" siblings
1267
+ * (the next nightly's crash-753ce4c1: the
1268
+ * map dissolved into four root scalars) */
1228
1269
  (void)yt_put(t, ((uint64_t)YTP_EV_COMPACT << 56) | 1u);
1229
1270
  (void)yt_put(t, ((uint64_t)YTP_EV_COMPACT << 56) | 3u);
1230
1271
  size_t pos = 0;
@@ -1293,6 +1334,8 @@ static int yt_f_run(yt_fused* F) {
1293
1334
  if (n1 == ' ' || n1 == '\t') {
1294
1335
  dash = 1;
1295
1336
  }
1337
+ } else if (c == '-' && line_indent + 1 >= line_end) {
1338
+ dash = 1; /* a line-end dash is an indicator, not the scalar "-" */
1296
1339
  }
1297
1340
  size_t next = line_end < len ? (size_t)line_end + 1 : len;
1298
1341
  if (!blank && !comment) {
@@ -1301,6 +1344,18 @@ static int yt_f_run(yt_fused* F) {
1301
1344
  if (c == '\t') {
1302
1345
  return 1; /* tab-led content: the engine's error */
1303
1346
  }
1347
+ /* document markers are the engine's alone: only it carries
1348
+ * the "..."+content error law (the nightly's marshal
1349
+ * crasher, "a: b\n... vi an : }" — the runner swallowed
1350
+ * the post-'...' content as a pair) */
1351
+ if (col == 0 && line_end - line_indent >= 3 &&
1352
+ (memcmp(p + line_indent, "...", 3) == 0 ||
1353
+ memcmp(p + line_indent, "---", 3) == 0)) {
1354
+ size_t mk = line_indent + 3;
1355
+ if (mk >= line_end || p[mk] == ' ' || p[mk] == '\t') {
1356
+ return 1;
1357
+ }
1358
+ }
1304
1359
  if (dash && depth > 0 && st[depth].pending && col >= st[depth].indent) {
1305
1360
  /* the pending key resolves into a sequence: its own
1306
1361
  * frame (the key's map survives below it) */
@@ -1340,6 +1395,28 @@ static int yt_f_run(yt_fused* F) {
1340
1395
  } else {
1341
1396
  uint8_t of = yt_f_open_form(&st[depth - 1], depth == 1, st[depth].indent);
1342
1397
  yt_f_open(F, &st[depth], of);
1398
+ if (depth == 1 && st[0].content_col == 0) {
1399
+ /* the root bookkeeping the anchored branch carries:
1400
+ * without it a dedent past the root map's column
1401
+ * pops to a content_col-0 st[0] and the pair arm
1402
+ * read "root pairs at column 0" — the nightly's
1403
+ * run 38128905644, " american:\n - Boston...\n
1404
+ * national:" split into PHANTOM DOCUMENTS
1405
+ * ("---\nb\n---\nv") while the engine rejected */
1406
+ st[0].content_col = st[depth].indent + 1;
1407
+ }
1408
+ if (st[depth].content_col == 0) {
1409
+ /* the opened map's pairs live at the KEY's
1410
+ * column (the anchored branch tracks this;
1411
+ * without it the off-column guard never fires
1412
+ * and a key line BETWEEN the resolved seq's
1413
+ * column and the parent's fell through to a
1414
+ * SILENTLY TRUNCATED document — the nightly's
1415
+ * run 38118641722, "ue:\n - one\n woc:\n
1416
+ * - one\n wt o- : thrt" dropped lines 3-5
1417
+ * while the engine rejected at 3:2) */
1418
+ st[depth].content_col = st[depth].indent + 1;
1419
+ }
1343
1420
  root_open = 1;
1344
1421
  st[depth].pending = 0;
1345
1422
  }
@@ -1358,6 +1435,22 @@ static int yt_f_run(yt_fused* F) {
1358
1435
  while (depth > 0 && (st[depth].kind == 1 ? (col < st[depth].indent ||
1359
1436
  (col <= st[depth].indent && !dash))
1360
1437
  : col < st[depth].indent)) {
1438
+ if (st[depth].kind == 1 && col == st[depth].indent && !dash &&
1439
+ !(depth > 1 && st[depth - 1].kind == 0 &&
1440
+ st[depth - 1].indent == st[depth].indent)) {
1441
+ return 1; /* a non-dash line at an INDENTED seq's
1442
+ * column: the seq owns its column, the
1443
+ * pair cannot join it and cannot close
1444
+ * it (libyaml: "did not find expected
1445
+ * '-' indicator"; the nightly's run
1446
+ * 38117287882, "amer:\n - j\n n:\n
1447
+ * - Bksl,} -- }" — the runner popped
1448
+ * the seq and SILENTLY DROPPED the
1449
+ * lines while the engine rejected).
1450
+ * Only the INDENTLESS seq — parent map
1451
+ * keys at the same column — closes on
1452
+ * a key line there ("k:\n- j\nn: v") */
1453
+ }
1361
1454
  if (st[depth].pending) {
1362
1455
  return 1; /* empty "key:" mid-doc: the engine owns it */
1363
1456
  }
@@ -1391,8 +1484,19 @@ static int yt_f_run(yt_fused* F) {
1391
1484
  vc == '?') {
1392
1485
  return 1;
1393
1486
  }
1394
- if (vc == '-' && vt + 1 < line_end && (p[vt + 1] == ' ' || p[vt + 1] == '\t')) {
1395
- return 1; /* nested dash: the engine's nested seq */
1487
+ if (!yep_plain_first_ok(vc)) {
1488
+ return 1; /* ']'/','/'}'/'@'/'`' first: no plain
1489
+ item can start there (the nightly's
1490
+ marshal crasher, "x:\n- ]\"#") — the
1491
+ engine errors cleanly */
1492
+ }
1493
+ if (vc == '-' && (vt + 1 >= line_end || p[vt + 1] == ' ' || p[vt + 1] == '\t' ||
1494
+ p[vt + 1] == '#')) {
1495
+ return 1; /* nested dash (incl. line-end — the
1496
+ nightly's "n:\n- -\t" crasher: a
1497
+ trailing bare '-' is an indicator,
1498
+ never the plain scalar "-"): the
1499
+ engine's nested seq */
1396
1500
  }
1397
1501
  /* compact map item "- k: v"? */
1398
1502
  for (size_t ic = vt; ic < vend; ic++) {
@@ -1424,6 +1528,40 @@ static int yt_f_run(yt_fused* F) {
1424
1528
  !(c == '&' || c == '!' || c == '*' || c == '\'' || c == '"' || c == '[' ||
1425
1529
  c == '{' || c == '|' || c == '>' || c == '%' || c == '?' || c == '-') &&
1426
1530
  yep_plain_first_ok((unsigned char)c)) {
1531
+ if (depth == 0) {
1532
+ if (root_pair_col != 0 && col < root_pair_col) {
1533
+ /* a dedent below the root map's own pair column:
1534
+ * a SECOND root without "---" (issue #630, " k:
1535
+ * &a\n - r\na: b" — the pending-value root arm
1536
+ * never arms content_col, so the off-column
1537
+ * guard below cannot see this; the engine and
1538
+ * libyaml reject with "did not find expected
1539
+ * <document start>") */
1540
+ return 1;
1541
+ }
1542
+ if (root_pair_col == 0) {
1543
+ root_pair_col = col; /* the first root pair owns its column */
1544
+ }
1545
+ }
1546
+ if (st[depth].kind == 0 &&
1547
+ ((st[depth].content_col != 0 && col + 1 != st[depth].content_col &&
1548
+ (col != st[depth].indent || (depth == 0 && st[0].content_col > 1))) ||
1549
+ (st[depth].content_col == 0 && depth > 0 && !st[depth].pending &&
1550
+ col != st[depth].indent))) {
1551
+ /* an OFF-COLUMN pair. No pending key awaits it, so a
1552
+ * more-indented line cannot open a child and a
1553
+ * dedented one is a second root (the catch-39 law;
1554
+ * libyaml: "mapping values are not allowed in this
1555
+ * context"). Two states: a frame with its own content
1556
+ * column rejects every other column; a frame whose
1557
+ * content map already closed (the phantom close above)
1558
+ * only accepts pairs at its own key column. The pop
1559
+ * loop pops on col < indent alone — without these
1560
+ * checks "a:\n b: 1\nc: 2\n d: 3" rode the stale
1561
+ * frame as a phantom sibling (the nightly's run
1562
+ * 38179552341) */
1563
+ return 1;
1564
+ }
1427
1565
  uint32_t kend = line_stop;
1428
1566
  while (kend > t0 && (p[kend - 1] == ' ' || p[kend - 1] == '\t')) {
1429
1567
  kend--;
@@ -1455,6 +1593,12 @@ static int yt_f_run(yt_fused* F) {
1455
1593
  return 1; /* an anchor needs a name (libyaml):
1456
1594
  the engine owns the error */
1457
1595
  }
1596
+ if (ne < line_end && p[ne] == '#') {
1597
+ return 1; /* a glued '#' is junk on the name
1598
+ (libyaml: "did not find expected
1599
+ alphabetic or numeric character") —
1600
+ the engine owns it */
1601
+ }
1458
1602
  F->anchor_seq++;
1459
1603
  aid = F->anchor_seq;
1460
1604
  if (!yt_anchor_table(F)) {
@@ -1471,8 +1615,24 @@ static int yt_f_run(yt_fused* F) {
1471
1615
  * (aid rides it: an anchored container resolves through
1472
1616
  * the S2900 + anchored-start form, not on_block_open) */
1473
1617
  if (st[depth].kind == 0 && st[depth].content_col != 0 &&
1474
- col + 1 != st[depth].content_col && col != st[depth].indent) {
1475
- return 1; /* off-column: a continuation line — the multiline arm */
1618
+ col + 1 != st[depth].content_col &&
1619
+ (col != st[depth].indent || (depth == 0 && st[0].content_col > 1))) {
1620
+ return 1; /* off-column: a continuation line — the multiline arm;
1621
+ * at the ROOT the indent exemption dies once pairs
1622
+ * live above column 0: a col-0 line under a col-1
1623
+ * root is a DEDENT — a second root without "---",
1624
+ * the engine's error (libyaml: "did not find
1625
+ * expected <document start>") */
1626
+ }
1627
+ if (st[depth].kind != 0) {
1628
+ return 1; /* a pending "key:" over a SEQ frame: the
1629
+ * key cannot join the sequence (libyaml
1630
+ * "mapping values are not allowed in
1631
+ * this context"; the nightly's run
1632
+ * 38127584324, "k:\n- 42\n bar:\n
1633
+ * - 45 e." — the pending pushed UNDER
1634
+ * the seq and materialized as an ITEM
1635
+ * while the engine rejected) */
1476
1636
  }
1477
1637
  if (st[depth].kind == 0 && st[depth].content_col != 0 &&
1478
1638
  col + 1 != st[depth].content_col && st[depth].phantom) {
@@ -1480,10 +1640,13 @@ static int yt_f_run(yt_fused* F) {
1480
1640
  st[depth].phantom = 0;
1481
1641
  }
1482
1642
  if (depth > 0 && st[depth].pending) {
1483
- uint8_t of = yt_f_open_form(&st[depth - 1], depth == 1, st[depth].indent);
1484
- yt_f_open(F, &st[depth], of);
1485
- root_open = 1;
1486
- st[depth].pending = 0;
1643
+ /* chained same-column pending ("a:\na:"): resolving
1644
+ * the first via yt_f_open corrupts the frame mapping
1645
+ * when the second's container carries valued pairs —
1646
+ * the OPEN'd map swallows the second key as the
1647
+ * first pair's value (the nightly's run 38110714729
1648
+ * tree corruption). The engine owns the shape */
1649
+ return 1;
1487
1650
  }
1488
1651
  /* a spent key-SCALAR frame (resolved, phantom closed,
1489
1652
  * no map of its own) gives its slot to the sibling —
@@ -1507,10 +1670,22 @@ static int yt_f_run(yt_fused* F) {
1507
1670
  st[depth].anchored_first = (uint8_t)(aid != 0);
1508
1671
  } else {
1509
1672
  if (st[depth].kind == 0 && st[depth].content_col != 0 &&
1510
- col + 1 != st[depth].content_col && col != st[depth].indent) {
1511
- return 1; /* off-column: a continuation line — the multiline arm */
1673
+ col + 1 != st[depth].content_col &&
1674
+ (col != st[depth].indent || (depth == 0 && st[0].content_col > 1))) {
1675
+ return 1; /* off-column: a continuation line — the multiline arm;
1676
+ * the root's indent exemption dies once pairs
1677
+ * live above column 0 (dedent = second root) */
1512
1678
  }
1513
1679
  if (depth > 0 && st[depth].pending && st[depth].aid != 0) {
1680
+ if (depth > 1) {
1681
+ /* an anchored pending BELOW the root frame: the
1682
+ * deferral's records only flush at depth 1 — a
1683
+ * deeper one drops its pairs at EOF ("4:\n4: r\n
1684
+ * 5:\n &5\n 5: e" lost the anchored map; the
1685
+ * nightly's run 38202382251) — the engine
1686
+ * resolves it */
1687
+ return 1;
1688
+ }
1514
1689
  if (col <= st[depth].indent) {
1515
1690
  return 1; /* same-column "content": the anchored value is
1516
1691
  * null and this line is a sibling — the engine */
@@ -1555,6 +1730,14 @@ static int yt_f_run(yt_fused* F) {
1555
1730
  } else if (depth > 0 && st[depth].pending) {
1556
1731
  uint8_t of = yt_f_open_form(&st[depth - 1], depth == 1, st[depth].indent);
1557
1732
  yt_f_open(F, &st[depth], of);
1733
+ if (depth == 1 && st[0].content_col == 0) {
1734
+ /* the pending->MAP resolution needs the same
1735
+ * root bookkeeping the seq arm gained (run
1736
+ * 38134498534, " e:\n f: g\nh: i" — the
1737
+ * dedent popped to a content_col-0 root and
1738
+ * the doc accepted what the engine rejects) */
1739
+ st[0].content_col = st[depth].indent + 1;
1740
+ }
1558
1741
  root_open = 1;
1559
1742
  st[depth].pending = 0;
1560
1743
  if (col == st[depth].indent) {
@@ -1578,6 +1761,10 @@ static int yt_f_run(yt_fused* F) {
1578
1761
  if (st[depth].phantom) {
1579
1762
  yt_f_close(F, st[depth].kind);
1580
1763
  st[depth].phantom = 0;
1764
+ st[depth].content_col = 0; /* the content map is
1765
+ * gone: its column must not
1766
+ * greet later pairs (the
1767
+ * stale frame below) */
1581
1768
  }
1582
1769
  } else {
1583
1770
  yt_f_start(F, &st[depth]);
@@ -1600,6 +1787,29 @@ static int yt_f_run(yt_fused* F) {
1600
1787
  }
1601
1788
  ne++;
1602
1789
  }
1790
+ if (ne < line_end && p[ne] == '#') {
1791
+ return 1; /* a glued '#' is junk on the
1792
+ * name (the engine errors:
1793
+ * "did not find expected
1794
+ * alphabetic or numeric
1795
+ * character") */
1796
+ }
1797
+ {
1798
+ size_t ae = ne;
1799
+ while (ae < line_end && (p[ae] == ' ' || p[ae] == '\t')) {
1800
+ ae++;
1801
+ }
1802
+ if (ae < line_end && p[ae] != '#') {
1803
+ return 1; /* an alias node is COMPLETE —
1804
+ * on the line only a blank
1805
+ * then a comment may follow
1806
+ * ("<<: *x &y": the engine
1807
+ * errors, libyaml "did not
1808
+ * find expected key"; this
1809
+ * branch silently dropped the
1810
+ * bytes) */
1811
+ }
1812
+ }
1603
1813
  uint32_t taid = 0;
1604
1814
  if (!yt_anchor_get(F, (uint32_t)(vt + 1), (uint32_t)(ne - vt - 1), &taid)) {
1605
1815
  return 1; /* undefined alias: the engine errors */
@@ -1662,10 +1872,25 @@ static int yt_f_run(yt_fused* F) {
1662
1872
  }
1663
1873
  if (vc == '|' && aid == 0 && !st[depth].anchored_first &&
1664
1874
  (vt + 1 >= line_end || p[vt + 1] == ' ' || p[vt + 1] == '\t')) {
1875
+ /* the header tail: only blanks and an optional
1876
+ * comment may follow the indicator (libyaml:
1877
+ * "did not find expected comment or line break";
1878
+ * the nightly's run 38119775008, "ext: | c* " —
1879
+ * this arm took the literal and SILENTLY DROPPED
1880
+ * the trailing header bytes while the engine
1881
+ * rejected) */
1882
+ size_t ht = vt + 1;
1883
+ while (ht < line_end && (p[ht] == ' ' || p[ht] == '\t')) {
1884
+ ht++;
1885
+ }
1886
+ if (ht < line_end && p[ht] != '#') {
1887
+ return 1;
1888
+ }
1665
1889
  /* bare literal block, clip chomp: measure the block,
1666
1890
  * copy it into the finish pool, emit the pool form */
1667
1891
  size_t scan = next;
1668
1892
  uint32_t bindent = 0;
1893
+ uint32_t lead_blank_max = 0;
1669
1894
  int have_bi = 0, have_content = 0;
1670
1895
  size_t clen = 0, tb = 0;
1671
1896
  size_t end_scan = scan;
@@ -1697,6 +1922,9 @@ static int yt_f_run(yt_fused* F) {
1697
1922
  /* a blank line: spaces past the block indent are
1698
1923
  * CONTENT; only stripped-empty lines clip away */
1699
1924
  size_t ind = ie - ls;
1925
+ if (!have_content && ind > lead_blank_max) {
1926
+ lead_blank_max = (uint32_t)ind;
1927
+ }
1700
1928
  size_t res = have_bi && ind > bindent ? ind - bindent : 0;
1701
1929
  clen += res + 1;
1702
1930
  if (res == 0) {
@@ -1712,6 +1940,22 @@ static int yt_f_run(yt_fused* F) {
1712
1940
  if (ind <= col) {
1713
1941
  break; /* dedented past the block */
1714
1942
  }
1943
+ if (ind < lead_blank_max) {
1944
+ return 1; /* a LEADING blank line
1945
+ * with MORE indent
1946
+ * floors the block
1947
+ * (libyaml's
1948
+ * auto-detect takes
1949
+ * the blanks' max;
1950
+ * content below the
1951
+ * floor breaks the
1952
+ * map — Psych: "did
1953
+ * not find expected
1954
+ * key"; the nightly's
1955
+ * run 38163867815,
1956
+ * "a: |\n \n b"):
1957
+ * the engine's error */
1958
+ }
1715
1959
  bindent = ind;
1716
1960
  have_bi = 1;
1717
1961
  } else if (ind < bindent) {
@@ -1735,7 +1979,9 @@ static int yt_f_run(yt_fused* F) {
1735
1979
  spilled = 1;
1736
1980
  }
1737
1981
  end_scan =
1738
- (le < len && p[le] == '\r' && le + 1 < len) ? le + 2 : le + 1;
1982
+ (le < len && p[le] == '\r' && le + 1 < len && p[le + 1] == '\n')
1983
+ ? le + 2
1984
+ : le + 1; /* lone CR is a 1-byte break */
1739
1985
  scan = end_scan;
1740
1986
  }
1741
1987
  if (!have_content) {
@@ -1748,7 +1994,14 @@ static int yt_f_run(yt_fused* F) {
1748
1994
  return 1;
1749
1995
  }
1750
1996
  }
1751
- char* dst = (char*)yep_pool_alloc(F->pool, flen, 16);
1997
+ /* the BLOCK is clen-sized: both copy paths write
1998
+ * every measured line (the clip lives in the
1999
+ * SPAN, not the copy) — allocating flen wrote
2000
+ * tb bytes past the block whenever a literal
2001
+ * ended with stripped-empty lines (the nightly's
2002
+ * run 38137087116 ASan heap-buffer-overflow,
2003
+ * WRITE at ytape.c's re-walk memcpy) */
2004
+ char* dst = (char*)yep_pool_alloc(F->pool, clen, 16);
1752
2005
  if (dst == NULL) {
1753
2006
  return 1;
1754
2007
  }
@@ -1795,8 +2048,10 @@ static int yt_f_run(yt_fused* F) {
1795
2048
  q += le - (ls + bindent);
1796
2049
  *q++ = '\n';
1797
2050
  }
1798
- s2 = (le < len && p[le] == '\r' && le + 1 < len) ? le + 2
1799
- : le + 1;
2051
+ s2 = (le < len && p[le] == '\r' && le + 1 < len &&
2052
+ p[le + 1] == '\n')
2053
+ ? le + 2
2054
+ : le + 1; /* lone CR is a 1-byte break */
1800
2055
  }
1801
2056
  }
1802
2057
  if ((size_t)(q - dst) != clen) {
@@ -51,6 +51,9 @@ YEPTRIS_API size_t yeptris_serialize_into_ex(YeptrisDocument handle,
51
51
  em.w.p = NULL;
52
52
  em.w.last = 0;
53
53
  em.w.force_flow = 0;
54
+ em.w.in_flow = 0;
55
+ em.w.flow_item = 0;
56
+ em.w.unserializable = 0;
54
57
  em.w.canonical = opts_canonical(opts);
55
58
  em.w.explicit_doc_start = opts_doc_marker(opts);
56
59
  em.w.json = 0;
@@ -116,6 +119,9 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
116
119
  em.w.p = NULL;
117
120
  em.w.last = 0;
118
121
  em.w.force_flow = 0;
122
+ em.w.in_flow = 0;
123
+ em.w.flow_item = 0;
124
+ em.w.unserializable = 0;
119
125
  em.w.canonical = opts_canonical(opts);
120
126
  em.w.explicit_doc_start = opts_doc_marker(opts);
121
127
  em.w.json = 0;
@@ -160,7 +166,7 @@ YEPTRIS_API char* yeptris_serialize_ex(YeptrisDocument handle, const yeptris_emi
160
166
  * any growth; every post-run touch goes through the writer's
161
167
  * pointer (the first round's abort was exactly this aliasing) */
162
168
  out = em.w.p;
163
- if (em.w.oom) {
169
+ if (em.w.oom || em.w.unserializable) {
164
170
  free(out);
165
171
  if (em.canon_names.sys != NULL) {
166
172
  yep_nametab_free(&em.canon_names);
@@ -200,6 +206,9 @@ YEPTRIS_API char* yeptris_serialize_json(YeptrisDocument handle, size_t* len) {
200
206
  em.w.p = NULL;
201
207
  em.w.last = 0;
202
208
  em.w.force_flow = 0;
209
+ em.w.in_flow = 0;
210
+ em.w.flow_item = 0;
211
+ em.w.unserializable = 0;
203
212
  em.w.canonical = 0;
204
213
  em.w.explicit_doc_start = 0; /* JSON output carries no --- */
205
214
  em.w.json = 1;
@@ -258,6 +267,9 @@ YEPTRIS_API char* yeptris_serialize_json_ex(YeptrisDocument handle, size_t* len,
258
267
  em.w.p = NULL;
259
268
  em.w.last = 0;
260
269
  em.w.force_flow = 0;
270
+ em.w.in_flow = 0;
271
+ em.w.flow_item = 0;
272
+ em.w.unserializable = 0;
261
273
  em.w.canonical = 0;
262
274
  em.w.explicit_doc_start = 0; /* JSON output carries no --- */
263
275
  em.w.json = 1;
@@ -310,6 +322,9 @@ char* yep_serialize_json_compact(const yeptris_document* doc, size_t* len) {
310
322
  em.w.p = NULL;
311
323
  em.w.last = 0;
312
324
  em.w.force_flow = 0;
325
+ em.w.in_flow = 0;
326
+ em.w.flow_item = 0;
327
+ em.w.unserializable = 0;
313
328
  em.w.canonical = 0;
314
329
  em.w.explicit_doc_start = 0; /* JSON output carries no --- */
315
330
  em.w.json = 1;
@@ -362,6 +377,9 @@ char* yep_serialize_json_pretty(const yeptris_document* doc, size_t* len) {
362
377
  em.w.p = NULL;
363
378
  em.w.last = 0;
364
379
  em.w.force_flow = 0;
380
+ em.w.in_flow = 0;
381
+ em.w.flow_item = 0;
382
+ em.w.unserializable = 0;
365
383
  em.w.canonical = 0;
366
384
  em.w.explicit_doc_start = 0; /* JSON output carries no --- */
367
385
  em.w.json = 1;
@@ -430,6 +448,9 @@ YEPTRIS_API size_t yeptris_serialize_stream(YeptrisDocument handle,
430
448
  em.w.p = NULL;
431
449
  em.w.last = 0;
432
450
  em.w.force_flow = 0;
451
+ em.w.in_flow = 0;
452
+ em.w.flow_item = 0;
453
+ em.w.unserializable = 0;
433
454
  em.w.canonical = opts_canonical(opts);
434
455
  em.w.explicit_doc_start = opts_doc_marker(opts);
435
456
  em.w.json = 0;
@@ -19,6 +19,13 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
19
19
  if (is_blank(p[0]) || is_blank(p[len - 1])) {
20
20
  return 0;
21
21
  }
22
+ if (len >= 3 && (unsigned char)p[0] == 0xef && (unsigned char)p[1] == 0xbb &&
23
+ (unsigned char)p[2] == 0xbf) {
24
+ /* a LEADING U+FEFF re-parses as a document BOM and vanishes —
25
+ * the emitted form must carry its escape ("\uFEFF", the
26
+ * nightly's run 38258674337); mid-token BOMs ride plain */
27
+ return 0;
28
+ }
22
29
  switch (p[0]) {
23
30
  case ':':
24
31
  /* libyaml (Psych symbol dumps ":name"): a leading colon only
@@ -119,7 +126,12 @@ int yep_style_plain_safe(const char* p, uint32_t len) {
119
126
  }
120
127
  }
121
128
  /* document markers */
122
- if ((len == 3 && (memcmp(p, "---", 3) == 0 || memcmp(p, "...", 3) == 0))) {
129
+ if (len >= 3 && (memcmp(p, "---", 3) == 0 || memcmp(p, "...", 3) == 0) &&
130
+ (len == 3 || p[3] == ' ' || p[3] == '\t' || p[3] == '\n' || p[3] == '\r')) {
131
+ /* a PREFIX marker is worse than the bare one: emitted plain at
132
+ * line start, "--- ..." re-reads as a document marker (the
133
+ * nightly's "--- --- >" folded-plain crasher) — libyaml quotes
134
+ * both */
123
135
  return 0;
124
136
  }
125
137
  return 1;