@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.
- package/INSTALL.md +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- 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
|
-
/*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
written
|
|
291
|
-
|
|
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 <
|
|
295
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1502
|
+
nish_fatal_exit();
|
|
1263
1503
|
}
|
|
1264
1504
|
|
|
1265
1505
|
/* ---- Checked arithmetic (Rust semantics): the failed-check paths. One table
|