@amritk/nish-x86_64-linux 0.18.0 → 0.21.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.
Files changed (104) hide show
  1. package/INSTALL.md +58 -28
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2759 -0
  4. package/package.json +3 -2
  5. package/runtime/browser.d.ts +14 -0
  6. package/runtime/browser.mjs +88 -0
  7. package/runtime/core/arrays.d.ts +18 -0
  8. package/runtime/core/arrays.mjs +125 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +70 -0
  11. package/runtime/core/host.mjs +311 -0
  12. package/runtime/core/numbers.d.ts +53 -0
  13. package/runtime/core/numbers.mjs +316 -0
  14. package/runtime/core/result.d.ts +41 -0
  15. package/runtime/core/result.mjs +127 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +99 -5
  22. package/runtime/nish.h +289 -19
  23. package/runtime/nish.mjs +39 -3
  24. package/runtime/node.mjs +383 -0
  25. package/runtime/runtime-aes.c +490 -0
  26. package/runtime/runtime-bytes.c +401 -0
  27. package/runtime/runtime-cross.c +366 -0
  28. package/runtime/runtime-host.c +8 -7
  29. package/runtime/runtime-net.c +93 -34
  30. package/runtime/runtime-os.c +214 -16
  31. package/runtime/runtime-parallel.c +590 -107
  32. package/runtime/runtime-simd.c +235 -11
  33. package/runtime/runtime-term.c +504 -0
  34. package/runtime/runtime-wasm.c +5 -14
  35. package/runtime/runtime.c +257 -17
  36. package/runtime/shim.mjs +6 -915
  37. package/runtime/sqlite/LICENSE.md +85 -0
  38. package/runtime/sqlite/README.md +45 -0
  39. package/runtime/sqlite/nish-sqlite.c +359 -0
  40. package/runtime/sqlite/sqlite3.c +271131 -0
  41. package/runtime/sqlite/sqlite3.h +14447 -0
  42. package/scripts/build.sh +437 -28
  43. package/std/LICENSE-fast-float +23 -0
  44. package/std/LICENSE-fdlibm +8 -0
  45. package/std/README.md +108 -58
  46. package/std/base64.ts +116 -0
  47. package/std/bytes.ts +419 -0
  48. package/std/cli.ts +328 -0
  49. package/std/collections.ts +6 -5
  50. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  51. package/std/crypto/LICENSE-libsecp256k1 +19 -0
  52. package/std/crypto/aes.ts +238 -76
  53. package/std/crypto/base64url.ts +125 -0
  54. package/std/crypto/bcrypt.ts +760 -0
  55. package/std/crypto/chacha20poly1305.ts +43 -9
  56. package/std/crypto/ed25519.ts +840 -0
  57. package/std/crypto/hkdf.ts +304 -12
  58. package/std/crypto/hmac.ts +382 -54
  59. package/std/crypto/p256.ts +932 -61
  60. package/std/crypto/x25519.ts +55 -37
  61. package/std/crypto/x509-random.ts +71 -0
  62. package/std/crypto/x509.ts +20 -131
  63. package/std/json-tokenizer.ts +4237 -0
  64. package/std/json-typed.ts +1014 -0
  65. package/std/json-value.ts +1182 -0
  66. package/std/json.ts +1202 -34
  67. package/std/math.ts +608 -0
  68. package/std/net/buffer-pool.ts +151 -0
  69. package/std/net/hpack.ts +416 -112
  70. package/std/net/http-fields.ts +93 -0
  71. package/std/net/http1-server.ts +501 -62
  72. package/std/net/http1.ts +31 -6
  73. package/std/net/http2-tls.ts +112 -24
  74. package/std/net/http2.ts +333 -75
  75. package/std/net/http3-server.ts +261 -43
  76. package/std/net/http3.ts +187 -28
  77. package/std/net/postgres.ts +1875 -0
  78. package/std/net/prefork.ts +412 -0
  79. package/std/net/quic-listener.ts +108 -11
  80. package/std/net/quic-packet.ts +243 -82
  81. package/std/net/quic-recovery.ts +361 -30
  82. package/std/net/quic-stream.ts +424 -24
  83. package/std/net/quic.ts +519 -167
  84. package/std/net/router.ts +676 -0
  85. package/std/net/tls/codec.ts +426 -27
  86. package/std/net/tls/record-server.ts +478 -132
  87. package/std/net/tls/record.ts +133 -42
  88. package/std/net/tls/schedule.ts +464 -55
  89. package/std/net/tls/ticket.ts +610 -0
  90. package/std/net/tls-tcp.ts +196 -53
  91. package/std/net/tls.ts +1018 -260
  92. package/std/net/url.ts +571 -0
  93. package/std/random.ts +94 -0
  94. package/std/regex.ts +4274 -0
  95. package/std/sqlite.ts +391 -0
  96. package/std/term-io.ts +978 -0
  97. package/std/term-pixels.ts +874 -0
  98. package/std/term-probe.ts +459 -0
  99. package/std/term-rate.ts +993 -0
  100. package/std/term.ts +2453 -0
  101. package/std/text.ts +90 -7
  102. package/std/threads.ts +438 -14
  103. package/std/yaml-lines.ts +141 -0
  104. package/std/yaml.ts +4675 -0
package/runtime/runtime.c CHANGED
@@ -18,6 +18,7 @@
18
18
  * both use `-flto`, and the cold paths that lost the inlining are cold.
19
19
  */
20
20
  #define _POSIX_C_SOURCE 200809L
21
+ #include <errno.h>
21
22
  #include <inttypes.h>
22
23
  #include <math.h>
23
24
  #include <stddef.h>
@@ -40,6 +41,25 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
40
41
 
41
42
  #define NISH_COLD __attribute__((noreturn, cold, noinline))
42
43
 
44
+ /* ---- AddressSanitizer build (`-DNISH_ASAN`, tests/differential/asan.js)
45
+ A bump arena hides every overflow from ASan: one 64 KB malloc holds many
46
+ blocks, so a read past one block lands in the next and nothing is poisoned.
47
+ Under `NISH_ASAN` each arena block is a malloc of its own (`nish_asan_alloc`,
48
+ which asan.js also points compiled code's inline bump at), so a read one past
49
+ it hits ASan's redzone; the bytes the 8-byte rounding adds, an array's spare
50
+ capacity and an array's block left behind by growth are poisoned by hand.
51
+ Off, the macro is empty and the file compiles to the same object code it
52
+ always did. What it cannot see: a `pop` leaves the popped element
53
+ addressable below `cap` (the pop is inline IR), and a string's NUL is one
54
+ addressable byte past its `length`. `nish_arena_used` reads differently
55
+ under ASan, since every block is a chunk of its own. */
56
+ #ifdef NISH_ASAN
57
+ #include <sanitizer/asan_interface.h>
58
+ #define NISH_POISON(p, n) __asan_poison_memory_region((p), (n))
59
+ #else
60
+ #define NISH_POISON(p, n) ((void)0)
61
+ #endif
62
+
43
63
  /* ---- Thread-local state (WP20 T0)
44
64
  The arena and the RNG seed are the only mutable process-wide objects here,
45
65
  and both are a data race the moment a second thread runs compiled code. With
@@ -69,6 +89,11 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
69
89
  allocates from compiled code. The asserts below are the contract, checked
70
90
  wherever this file is compiled. */
71
91
  typedef struct nish_chunk { struct nish_chunk *next; uint64_t cap; } nish_chunk;
92
+ /* runtime-cross.c walks the chunks through a copy of this definition, which
93
+ asserts the same three facts. */
94
+ _Static_assert(sizeof(nish_chunk) == 16, "runtime-cross.c's nish_xchunk: { next, cap }, the bytes after it");
95
+ _Static_assert(offsetof(nish_chunk, next) == 0, "runtime-cross.c's nish_xchunk: next first");
96
+ _Static_assert(offsetof(nish_chunk, cap) == 8, "runtime-cross.c's nish_xchunk: cap second");
72
97
  struct nish_arena { char *buf; uint64_t off; uint64_t cap; nish_chunk *chunks; };
73
98
  /* One per thread under -DNISH_THREADS, one per process otherwise; the layout is
74
99
  the same either way, which is why nothing else in this file has to know. */
@@ -79,14 +104,68 @@ _Static_assert(offsetof(struct nish_arena, off) == 8, "the inlined allocator bum
79
104
  _Static_assert(offsetof(struct nish_arena, cap) == 16, "the inlined allocator reads field 2");
80
105
  _Static_assert(offsetof(struct nish_arena, chunks) == 24, "arena layout is ABI");
81
106
 
82
- /* `fputs` rather than one `write`: libc's own loop finishes a short write,
83
- which a bare `write` would cut off (docs/security/runtime.md, RT-11), and
84
- stderr is unbuffered, so the message leaves before `_exit`. A C host that
85
- gives stderr a buffer of its own would lose it here. */
86
- static NISH_COLD void nish_die(const char *msg) {
87
- fputs(msg, stderr);
107
+ /* ---- Panics: the first one wins.
108
+
109
+ Every way a program fails -- a failed check, `panic`, out of memory -- calls
110
+ `nish_panic_claim` before it prints. Two tasks of a scope, or two workers of
111
+ a parallel map, can fail at once, and without the claim both messages would
112
+ reach stderr interleaved and both threads would race to the exit. The first
113
+ to claim prints its whole message and exits; any later one parks until
114
+ that exit ends the process, so exactly one message is printed and the exit
115
+ code is the first panic's. How each path exits is unchanged by the claim:
116
+ `panic`, `expect` and a failed range entry (`nish_panic`) call `exit(1)`, as
117
+ they did through `nish_exit`, so atexit handlers run and stdio is flushed;
118
+ a failed check and out of memory call `_exit(1)`, as they always have. Only
119
+ one thread can reach either exit, because the claim comes first. Only a
120
+ threads build (`-DNISH_THREADS`) can have two threads panic, so only it
121
+ pays for the claim; everywhere else it is an empty inline function. Under wasi with threads, the trap is the
122
+ defensive answer: no host there gives a parked thread a way to wait. */
123
+ #ifdef NISH_THREADS
124
+ static int nish_panicked;
125
+ void nish_panic_claim(void) {
126
+ if (__atomic_exchange_n(&nish_panicked, 1, __ATOMIC_ACQ_REL)) {
127
+ #if defined(__wasi__)
128
+ __builtin_trap();
129
+ #else
130
+ for (;;) pause();
131
+ #endif
132
+ }
133
+ }
134
+ #else
135
+ static inline void nish_panic_claim(void) {}
136
+ #endif
137
+
138
+ /* How a panic that skips atexit ends: `_exit(1)`, after `nish_fatal_hook` when
139
+ it is set. `setRawMode(true)` and `setTerminalRestore` (runtime-term.c) set
140
+ the hook to the terminal's restore, and without it a failed bounds check
141
+ would leave the shell in raw mode. Each
142
+ panic path calls this where it called `_exit(1)`, so a call site costs no
143
+ more than it did; `exit(1)` paths need nothing, since the restore is also an
144
+ atexit handler. Built with `-DNISH_FUZZ_ABORT` (scripts/fuzz.sh), this and
145
+ `nish_panic` end in `abort()` instead, so libFuzzer records a panic, a failed
146
+ bounds or division check and an out-of-memory as a crash; the default build
147
+ compiles neither. */
148
+ __attribute__((noreturn)) void nish_fatal_exit(void);
149
+ void (*nish_fatal_hook)(void);
150
+ void nish_fatal_exit(void) {
151
+ void (*hook)(void) = __atomic_load_n(&nish_fatal_hook, __ATOMIC_ACQUIRE);
152
+ if (hook) hook();
153
+ #ifdef NISH_FUZZ_ABORT
154
+ abort();
155
+ #endif
88
156
  _exit(1);
89
157
  }
158
+
159
+ /* The message goes out through `nish_write_all`, below, which finishes a
160
+ short write and retries one a signal interrupts (docs/security/runtime.md,
161
+ RT-11); it is unbuffered, so the message leaves before `_exit`. It used
162
+ `fputs`, whose libc loop finishes a short write but gives up on `EINTR`. */
163
+ static void nish_write_all(int fd, const char *p, uint64_t n);
164
+ static NISH_COLD void nish_die(const char *msg) {
165
+ nish_panic_claim();
166
+ nish_write_all(2, msg, strlen(msg));
167
+ nish_fatal_exit();
168
+ }
90
169
  /* A function rather than a macro around `nish_die`: seven call sites each
91
170
  loading the message's address cost more than one more symbol does. */
92
171
  static NISH_COLD void nish_oom(void) { nish_die("nish: out of memory\n"); }
@@ -97,7 +176,13 @@ static NISH_COLD void nish_oom(void) { nish_die("nish: out of memory\n"); }
97
176
 
98
177
  /* Slow path (size 8-byte rounded): push a chunk (>= 64 KB), bump from it. */
99
178
  void *nish_arena_grow(uint64_t size) {
179
+ #ifdef NISH_ASAN
180
+ /* An exact chunk leaves off == cap, so the inline bump never fits again and
181
+ every block comes through here: one malloc, one redzone, per block. */
182
+ uint64_t cap = size;
183
+ #else
100
184
  uint64_t cap = size > 65536 ? size : 65536;
185
+ #endif
101
186
  /* A 64-bit request cannot always be asked for: wasm32's `size_t` is 32 bits,
102
187
  and malloc would silently take the low half of a chunk nobody can address. */
103
188
  if (cap > (uint64_t)(SIZE_MAX - sizeof(nish_chunk))) nish_oom();
@@ -112,8 +197,22 @@ void *nish_arena_grow(uint64_t size) {
112
197
  return nish_arena.buf;
113
198
  }
114
199
 
200
+ #ifdef NISH_ASAN
201
+ /* An instrumented program's every arena block: an exact chunk, so the inline
202
+ bump never fits again, with the bytes the rounding adds poisoned. */
203
+ void *nish_asan_alloc(uint64_t size) {
204
+ uint64_t rounded = (size + 7) & ~(uint64_t)7;
205
+ char *p = nish_arena_grow(rounded);
206
+ NISH_POISON(p + size, rounded - size);
207
+ return p;
208
+ }
209
+ #endif
210
+
115
211
  /* Bump allocation, 8-byte rounded/aligned, uninitialised; the compiler inlines it. */
116
212
  void *nish_alloc_struct(uint64_t size) {
213
+ #ifdef NISH_ASAN
214
+ return nish_asan_alloc(size);
215
+ #endif
117
216
  size = (size + 7) & ~(uint64_t)7;
118
217
  if (nish_arena.off + size <= nish_arena.cap) {
119
218
  void *p = nish_arena.buf + nish_arena.off;
@@ -123,12 +222,91 @@ void *nish_alloc_struct(uint64_t size) {
123
222
  return nish_arena_grow(size);
124
223
  }
125
224
 
225
+ #ifdef NISH_ASAN
226
+ /* The capacity a normal build would give each array `nish_array_grow` grew.
227
+ `cap` stays `len + 1`, since the push writes `data[len]` inline and spare
228
+ capacity would be addressable, but growing one slot at a time copies the
229
+ whole array on every push. So a block is allocated at the capacity a normal
230
+ build would choose, its tail poisoned, and each later grow of the same array
231
+ unpoisons one more slot in place until that capacity is used up. The block
232
+ then moves at exactly the push where a normal build reallocates, so a
233
+ pointer kept across that push reads poison.
234
+ The table is keyed by the grown block and loses an entry only when that
235
+ block dies: the grow that moves it, or `nish_free_until` freeing its chunk
236
+ (every ASan block is a chunk of its own). It is one table for every thread,
237
+ behind a spin lock, so an array grown on one thread and pushed on another
238
+ still hits. A miss is therefore an array no grow has moved yet, whose `cap`
239
+ is the one a normal build gave it, so the block never moves late. Open
240
+ addressing, linear probes, a tombstone for a dead entry, rebuilt at half
241
+ full. */
242
+ #define NISH_GROWN_DEAD ((char *)1)
243
+ static struct nish_grown { char *data; const void *owner; uint64_t native; } *nish_grown;
244
+ static uint64_t nish_grown_cap, nish_grown_used, nish_grown_live; /* used = live + tombstones */
245
+ static int nish_grown_lock;
246
+
247
+ static void nish_grown_enter(void) {
248
+ while (__atomic_exchange_n(&nish_grown_lock, 1, __ATOMIC_ACQUIRE)) {}
249
+ }
250
+ static void nish_grown_leave(void) { __atomic_store_n(&nish_grown_lock, 0, __ATOMIC_RELEASE); }
251
+
252
+ static uint64_t nish_grown_hash(const char *data) {
253
+ return ((uintptr_t)data * 0x9E3779B97F4A7C15ull >> 32) & (nish_grown_cap - 1);
254
+ }
255
+
256
+ static struct nish_grown *nish_grown_find(const char *data) {
257
+ if (!nish_grown_cap || !data) return 0;
258
+ for (uint64_t i = nish_grown_hash(data);; i = (i + 1) & (nish_grown_cap - 1)) {
259
+ if (nish_grown[i].data == data) return &nish_grown[i];
260
+ if (!nish_grown[i].data) return 0;
261
+ }
262
+ }
263
+
264
+ static void nish_grown_drop(const char *data) {
265
+ struct nish_grown *g = nish_grown_find(data);
266
+ if (g) g->data = NISH_GROWN_DEAD, nish_grown_live--;
267
+ }
268
+
269
+ static void nish_grown_put(char *data, const void *owner, uint64_t native) {
270
+ if ((nish_grown_used + 1) * 2 > nish_grown_cap) {
271
+ struct nish_grown *old = nish_grown;
272
+ uint64_t n = nish_grown_cap, cap = 64;
273
+ while (cap < (nish_grown_live + 1) * 4) cap *= 2;
274
+ nish_grown = calloc(cap, sizeof *nish_grown);
275
+ if (!nish_grown) nish_oom();
276
+ nish_grown_cap = cap, nish_grown_used = nish_grown_live = 0;
277
+ for (uint64_t i = 0; i < n; i++)
278
+ if (old[i].data > NISH_GROWN_DEAD) nish_grown_put(old[i].data, old[i].owner, old[i].native);
279
+ free(old);
280
+ }
281
+ uint64_t i = nish_grown_hash(data);
282
+ while (nish_grown[i].data > NISH_GROWN_DEAD) i = (i + 1) & (nish_grown_cap - 1);
283
+ if (!nish_grown[i].data) nish_grown_used++;
284
+ nish_grown[i] = (struct nish_grown){ data, owner, native };
285
+ nish_grown_live++;
286
+ }
287
+ #endif
288
+ #ifdef NISH_ASAN
289
+ void nish_free_arena(void);
290
+ #endif
126
291
  static void nish_free_until(nish_chunk *c, const nish_chunk *end) {
292
+ #ifdef NISH_ASAN
293
+ /* A freed block's address comes back from malloc: forget the grown ones. */
294
+ if (nish_grown_live) {
295
+ nish_grown_enter();
296
+ for (nish_chunk *d = c; d != end; d = d->next) nish_grown_drop((char *)(d + 1));
297
+ nish_grown_leave();
298
+ }
299
+ #endif
127
300
  while (c != end) { nish_chunk *n = c->next; free(c); c = n; }
128
301
  }
129
302
 
130
303
  /* O(1) recycle: keep the newest chunk, free the rest. */
131
304
  void nish_reset_arena(void) {
305
+ #ifdef NISH_ASAN
306
+ /* Free everything, so a value read after the reset is a use after free. */
307
+ nish_free_arena();
308
+ return;
309
+ #endif
132
310
  nish_chunk *keep = nish_arena.chunks;
133
311
  if (!keep) return;
134
312
  nish_free_until(keep->next, 0);
@@ -283,19 +461,38 @@ int64_t nish_str_index_of(const nish_str *s, const nish_str *sub) { return nish_
283
461
  does the same. So the bytes are written until all of them are out, or until
284
462
  a write fails (docs/security/runtime.md, RT-11). A failure still ends the
285
463
  output, `EAGAIN` on a non-blocking stdout included, since spinning on it
286
- would burn a core. `EINTR` is not retried here: it needs a handler
287
- installed without `SA_RESTART`, and the only one this runtime installs
288
- (runtime-host.c) has it, so an interrupted write either restarts in the
289
- kernel or comes back short. The newline needs no loop: one byte is never
290
- written in part. */
291
- void nish_write(const nish_str *s, int32_t fd, _Bool newline) {
464
+ would burn a core. `EINTR` is retried, the newline's included: the only
465
+ handlers this runtime installs that return (runtime-host.c's and
466
+ runtime-term.c's SIGTSTP) have `SA_RESTART`, but a C host may install one
467
+ without it. `panic`'s message, `expect`'s, and the
468
+ arithmetic and out-of-memory panics (`nish_die`) are written here. The
469
+ index and slice panics and the file and entropy failures use `dprintf`, and
470
+ runtime-parallel.c's out of memory one `write`: neither retries `EINTR`. */
471
+ static void nish_write_all(int fd, const char *p, uint64_t n) {
292
472
  uint64_t done = 0;
293
473
  ssize_t k;
294
- while (done < s->len && (k = write(fd, s->data + done, s->len - done)) > 0) done += k;
295
- if (newline) (void)!write(fd, "\n", 1);
474
+ while (done < n) {
475
+ k = write(fd, p + done, n - done);
476
+ if (k > 0) done += k;
477
+ else if (k == 0 || errno != EINTR) break;
478
+ }
479
+ }
480
+ void nish_write(const nish_str *s, int32_t fd, _Bool newline) {
481
+ nish_write_all(fd, s->data, s->len);
482
+ if (newline) nish_write_all(fd, "\n", 1);
296
483
  }
297
484
  /* console.log(s) */
298
485
  void nish_print(const nish_str *s) { nish_write(s, 1, 1); }
486
+ /* `panic(message)`, `r.expect(message)` and a failed range entry: the message
487
+ and a newline on stderr, then `exit(1)`, the first panic only. */
488
+ __attribute__((noreturn)) void nish_panic(const nish_str *s) {
489
+ nish_panic_claim();
490
+ nish_write(s, 2, 1);
491
+ #ifdef NISH_FUZZ_ABORT
492
+ abort();
493
+ #endif
494
+ exit(1);
495
+ }
299
496
 
300
497
  /* Decimal digits of `u`, with a '-' in front when `neg`. 20 digits is the
301
498
  widest a uint64_t can be, plus the sign. */
@@ -1165,10 +1362,18 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
1165
1362
  this first. */
1166
1363
  nish_array *nish_argv;
1167
1364
 
1365
+ /* The key of every `Map` and `Set` string hash (docs/security/json.md, JSON-13):
1366
+ SipHash-1-3's 128 bits, zero until the constructor each module that hashes
1367
+ a string registers fills it from `getentropy` before `main`. Data, not code,
1368
+ so it costs this file's `.text` budget nothing, and not NISH_TLS, for the
1369
+ reason `nish_argv` is not: it is written once before any thread exists, and
1370
+ a table one thread fills is probed by another under the same key. */
1371
+ uint64_t nish_hash_key[2];
1372
+
1168
1373
  void nish_argv_init(int32_t argc, char **argv) {
1169
1374
  nish_array *a = malloc(sizeof *a + argc * sizeof(nish_str *));
1170
- nish_str **d = (nish_str **)(a + 1);
1171
1375
  if (!a) nish_oom();
1376
+ nish_str **d = (nish_str **)(a + 1);
1172
1377
  *a = (nish_array){ argc, argc, (char *)d };
1173
1378
  nish_argv = a;
1174
1379
  while (argc-- > 0) {
@@ -1219,13 +1424,46 @@ double nish_parse_number(const nish_str *s, int32_t mode) {
1219
1424
  * line, every one of those rows runs fewer instructions than it did before, and
1220
1425
  * the call costs nothing on a path that runs once per doubling. */
1221
1426
  __attribute__((noinline)) void nish_array_grow(nish_array *a, uint64_t elem_size) {
1427
+ #ifdef NISH_ASAN
1428
+ /* One more slot, not double (see `nish_grown`): in place while a normal build
1429
+ would still have room, moved where it would reallocate. */
1430
+ uint64_t native = a->cap, cap = a->len + 1;
1431
+ nish_grown_enter();
1432
+ struct nish_grown *g = nish_grown_find(a->data);
1433
+ if (g && g->owner == a) native = g->native;
1434
+ else g = 0;
1435
+ if (g && cap <= native) {
1436
+ nish_grown_leave();
1437
+ __asan_unpoison_memory_region(a->data + a->len * elem_size, elem_size);
1438
+ a->cap = cap;
1439
+ return;
1440
+ }
1441
+ uint64_t room = native ? native * 2 : 4;
1442
+ if (room > NISH_ARRAY_MAX) room = NISH_ARRAY_MAX;
1443
+ #else
1222
1444
  uint64_t cap = a->cap ? a->cap * 2 : 4;
1445
+ #endif
1223
1446
  if (cap > NISH_ARRAY_MAX) {
1224
1447
  if (a->len >= NISH_ARRAY_MAX) nish_oom();
1225
1448
  cap = NISH_ARRAY_MAX;
1226
1449
  }
1450
+ #ifdef NISH_ASAN
1451
+ if (room < cap) room = cap;
1452
+ char *data = nish_alloc_struct(room * elem_size);
1453
+ NISH_POISON(data + cap * elem_size, (room - cap) * elem_size);
1454
+ #else
1227
1455
  char *data = nish_alloc_struct(cap * elem_size);
1456
+ #endif
1228
1457
  if (a->len) memcpy(data, a->data, a->len * elem_size);
1458
+ #ifdef NISH_ASAN
1459
+ /* The old block is dead: a pointer kept across the push now reads poison.
1460
+ Only a heap block, as a literal's may be on the stack or a constant. */
1461
+ const char *kind = a->cap ? __asan_locate_address(a->data, 0, 0, 0, 0) : 0;
1462
+ if (kind && !strcmp(kind, "heap")) NISH_POISON(a->data, (g ? native : a->cap) * elem_size);
1463
+ if (g) nish_grown_drop(a->data);
1464
+ nish_grown_put(data, a, room);
1465
+ nish_grown_leave();
1466
+ #endif
1229
1467
  a->data = data;
1230
1468
  a->cap = cap;
1231
1469
  }
@@ -1245,8 +1483,9 @@ __attribute__((noinline)) void nish_wipe(nish_array *bytes) {
1245
1483
  }
1246
1484
 
1247
1485
  void nish_panic_index(uint64_t idx, uint64_t len) {
1486
+ nish_panic_claim();
1248
1487
  dprintf(2, "index out of range: %" PRIu64 " >= %" PRIu64 "\n", idx, len);
1249
- _exit(1);
1488
+ nish_fatal_exit();
1250
1489
  }
1251
1490
 
1252
1491
  /* `s.slice(start, end)` (WP15 section 4) with a range the string does not
@@ -1258,8 +1497,9 @@ void nish_panic_index(uint64_t idx, uint64_t len) {
1258
1497
  a reader who wrote `s.slice(i - 1)` wants to be told `-1` rather than
1259
1498
  18446744073709551615. The length cannot be negative either way. */
1260
1499
  void nish_panic_slice(int64_t start, int64_t end, int64_t len) {
1500
+ nish_panic_claim();
1261
1501
  dprintf(2, "slice out of range: [%" PRId64 ", %" PRId64 ") of length %" PRId64 "\n", start, end, len);
1262
- _exit(1);
1502
+ nish_fatal_exit();
1263
1503
  }
1264
1504
 
1265
1505
  /* ---- Checked arithmetic (Rust semantics): the failed-check paths. One table