yeptris 0.6.42.0 → 0.6.43.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7cda6785f823f016de5e8fedc14d17037af6e79edd697d801172adf5a81142d
4
- data.tar.gz: 7a0d69532d73b0300286f47e9512c51511aa79f82a25c0014ad10dae0b9b509f
3
+ metadata.gz: 5fe6c4145817f5a8c4dd753ea92f523a41ebcef08ae6fc17cf12a4be8d5fcc7e
4
+ data.tar.gz: 46989aada81d5d8cb75ea73d51af0be671326041fec93ca03ba2184fb98fc0f0
5
5
  SHA512:
6
- metadata.gz: da390e1cab7318047c491e1230398a248d7a55517ef6effec0100912a284ddd6ff35ee78f8a6a8c3c7fe3ecd7dffe952b67ff8fe3bd4748f1d50dd95b4b267e6
7
- data.tar.gz: 9bb34364236adc7dab714d3020efca49ddac8b986baa9d71041efc13151a35f9bde8788e92cd80fbefd38d07b62e4e972ca3b1bdccebb9719c8e40d93c7521bf
6
+ metadata.gz: a896a1a0a23c9ab475f1dd642e44cc0dd30288416da97f67b2b6d037957c70cee08e3c37b6e76d09b21ba71884f87759cf460536d5033e988ea1b4cdd299b7d4
7
+ data.tar.gz: 8838bb5ee6f826da3ecb17316c328cf53ecd690e89f927a7c579b04ac62d17e31ca23e7bf0fcfe7d3ecd17e020223021d9638fa095c1aff4967db128ec552746
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.42.0".freeze
6
+ VERSION = "0.6.43.0".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
@@ -3,7 +3,7 @@
3
3
  cmake_minimum_required(VERSION 3.20)
4
4
 
5
5
  project(yeptris
6
- VERSION 0.6.42
6
+ VERSION 0.6.43
7
7
  DESCRIPTION "Ultra-fast YAML 1.2 parser, emitter and streamer in C"
8
8
  LANGUAGES C CXX
9
9
  )
@@ -66,6 +66,8 @@ static void wr_col_update(yep_writer* w, const char* p, uint32_t n) {
66
66
  w->col += (int)n;
67
67
  }
68
68
 
69
+ static void wr_reserve(yep_writer* w, size_t need);
70
+
69
71
  static void wr_grow(yep_writer* w, size_t need) {
70
72
  if (w->oom || w->p == NULL) {
71
73
  w->oom = w->p == NULL;
@@ -98,20 +100,53 @@ static void wr_put(yep_writer* w, const char* p, uint32_t n) {
98
100
  * (len + n == cap) put that NUL one byte past the buffer — the
99
101
  * first byte of the next heap block's header (the #182 flake:
100
102
  * corrupt deterministic, detected later, timing-varying) */
101
- if (w->grow && w->len + n + 1 > w->cap) {
102
- if (w->bounded) {
103
- w->oom = 1; /* keep counting: the trusted caller reads the size */
104
- } else {
105
- wr_grow(w, n);
103
+ if (w->reserve < (size_t)n) {
104
+ wr_reserve(w, n);
105
+ if (w->oom) {
106
+ w->len += n; /* keep counting: the caller fails on oom */
107
+ return;
106
108
  }
107
109
  }
108
- if (w->oom) {
109
- w->len += n; /* keep counting: the caller fails on oom */
110
- return;
110
+ if ((size_t)n <= w->reserve) {
111
+ w->reserve -= n;
112
+ } else {
113
+ w->reserve = 0; /* grow=0 trusts the count: the write rides past
114
+ * the bookkeeping exactly as before */
111
115
  }
112
116
  memcpy(w->p + w->len, p, n);
113
117
  w->len += n;
118
+ }
119
+
120
+ /* #739 lane 1: refill the writable reservation. The grow and flush
121
+ * checks move from every byte to every refill; the watermark law is
122
+ * unchanged in spirit (append-only output — any crossing is a safe
123
+ * cut, a sink just sees chunks up to one reserve past the mark).
124
+ * ONLY the growing routes hold a reservation: the caller-buffer and
125
+ * trusted-count routes keep cap bookkeeping that is not the writer's
126
+ * to reason about (several set cap=0), so their writes stay exactly
127
+ * the old per-call trust. */
128
+ static void wr_reserve(yep_writer* w, size_t need) {
129
+ /* the FLUSH runs for every route: the fixed-window streaming route
130
+ * (grow=0) has no reservation, so this refill path is its ONLY
131
+ * flush point -- skipping it let len ride to cap and the next
132
+ * write landed past the window (ASan: heap-buffer-overflow WRITE
133
+ * of size 2 exactly 0 bytes after the 128K region; ubuntu's
134
+ * allocator freed-corrupted, macOS's slack absorbed it). */
114
135
  wr_maybe_flush(w);
136
+ if (!w->grow || w->bounded) {
137
+ w->reserve = 0;
138
+ return;
139
+ }
140
+ if (w->oom) {
141
+ return;
142
+ }
143
+ if (w->len + need + 1 > w->cap) { /* +1: the terminator */
144
+ wr_grow(w, need);
145
+ if (w->oom) {
146
+ return;
147
+ }
148
+ }
149
+ w->reserve = w->cap - w->len - 1;
115
150
  }
116
151
 
117
152
  static void wr_byte(yep_writer* w, char c) {
@@ -125,20 +160,17 @@ static void wr_byte(yep_writer* w, char c) {
125
160
  w->len++;
126
161
  return;
127
162
  }
128
- if (w->grow && w->len + 2 > w->cap) { /* +1 write, +1 terminator */
129
- if (w->bounded) {
130
- w->oom = 1; /* keep counting: the trusted caller reads the size */
131
- } else {
132
- wr_grow(w, 1);
163
+ if (w->reserve == 0) {
164
+ wr_reserve(w, 1);
165
+ if (w->oom) {
166
+ w->len++;
167
+ return;
133
168
  }
134
169
  }
135
- if (w->oom) {
136
- w->len++;
137
- return;
170
+ if (w->reserve > 0) {
171
+ w->reserve--; /* grow=0 routes keep it at 0: per-call trust */
138
172
  }
139
- w->p[w->len] = c;
140
- w->len++;
141
- wr_maybe_flush(w);
173
+ w->p[w->len++] = c;
142
174
  }
143
175
 
144
176
  static void wr_indent(yep_writer* w, int depth) {
@@ -299,21 +331,34 @@ static void emit_dq_json(yep_writer* w, const char* p, uint32_t n) {
299
331
  NULL, "\\f", "\\r", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
300
332
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
301
333
  };
334
+ static const char hd[] = "0123456789abcdef";
335
+ /* run-batched (the #352 emit_dq shape): clean spans ride one
336
+ * wr_put each — per-byte wr_byte calls were 14% of the json
337
+ * write lane's leaf samples (yeptris#739) */
338
+ uint32_t run = 0;
302
339
  for (uint32_t i = 0; i < n; i++) {
303
340
  unsigned char c = (unsigned char)p[i];
341
+ if (c >= 0x20 && c != '"' && c != '\\') {
342
+ run++;
343
+ continue;
344
+ }
345
+ if (run) {
346
+ wr_put(w, p + i - run, run);
347
+ run = 0;
348
+ }
304
349
  if (c < 32 && named[c] != NULL) {
305
350
  wr_put(w, named[c], 2);
306
351
  } else if (c == '"' || c == '\\') {
307
352
  wr_byte(w, '\\');
308
353
  wr_byte(w, (char)c);
309
- } else if (c < 0x20) {
310
- static const char hd[] = "0123456789abcdef";
354
+ } else {
311
355
  char hex[6] = {'\\', 'u', '0', '0', hd[(c >> 4) & 0xf], hd[c & 0xf]};
312
356
  wr_put(w, hex, 6);
313
- } else {
314
- wr_byte(w, (char)c);
315
357
  }
316
358
  }
359
+ if (run) {
360
+ wr_put(w, p + n - run, run);
361
+ }
317
362
  wr_byte(w, '"');
318
363
  }
319
364
 
@@ -1101,6 +1146,9 @@ static void emit_block_map(yep_emitter* em, uint32_t id, int content_col) {
1101
1146
  if (w->oom) {
1102
1147
  w->len += total; /* keep counting: the caller fails on oom */
1103
1148
  } else {
1149
+ /* the fused write bypasses wr_put/wr_byte: keep
1150
+ * the reservation honest (the #739 lane-1 law) */
1151
+ w->reserve = (size_t)total <= w->reserve ? w->reserve - (size_t)total : 0;
1104
1152
  char* q = w->p + w->len;
1105
1153
  if (nl) {
1106
1154
  *q++ = '\n';
@@ -1360,6 +1408,7 @@ size_t yep_emit_run(yep_emitter* em, int dry) {
1360
1408
  const yep_dom* d = em->doc->dom;
1361
1409
  w->dry = dry;
1362
1410
  w->len = 0;
1411
+ w->reserve = 0;
1363
1412
  w->last = 0;
1364
1413
  w->force_flow = 0;
1365
1414
  w->flushed = 0;
@@ -57,9 +57,13 @@ typedef struct yep_writer {
57
57
  size_t cap;
58
58
  int grow;
59
59
  int oom;
60
- int bounded; /* #352: caller-owned fixed buffer — an oversized write
61
- sets oom and keeps counting (the needed size falls
62
- out of the same pass); wr_grow is never called */
60
+ int bounded; /* #352: caller-owned fixed buffer — an oversized write
61
+ sets oom and keeps counting (the needed size falls
62
+ out of the same pass); wr_grow is never called */
63
+ /* #739 lane 1: writable bytes guaranteed at w->p + w->len. The
64
+ * grow/flush checks run once per refill instead of once per byte;
65
+ * 0 at run entry and whenever the guarantee lapses. */
66
+ size_t reserve;
63
67
  int in_flow; /* scalars inside flow collections: plain must drop
64
68
  ',', '[', ']', '{', '}' — flow terminators */
65
69
  int flow_item; /* the NEXT scalar is a flow-SEQ item: a null must
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.42.0
4
+ version: 0.6.43.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-24 00:00:00.000000000 Z
11
+ date: 2026-10-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi