@amritk/nish-x86_64-linux 0.20.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 +22 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +2 -1
- 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 +44 -0
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- 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 +261 -53
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +31 -11
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +83 -1
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +85 -8
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +1875 -0
- package/std/net/quic-listener.ts +20 -4
- 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/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/runtime/runtime.c
CHANGED
|
@@ -41,6 +41,25 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
|
|
|
41
41
|
|
|
42
42
|
#define NISH_COLD __attribute__((noreturn, cold, noinline))
|
|
43
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
|
+
|
|
44
63
|
/* ---- Thread-local state (WP20 T0)
|
|
45
64
|
The arena and the RNG seed are the only mutable process-wide objects here,
|
|
46
65
|
and both are a data race the moment a second thread runs compiled code. With
|
|
@@ -116,6 +135,27 @@ void nish_panic_claim(void) {
|
|
|
116
135
|
static inline void nish_panic_claim(void) {}
|
|
117
136
|
#endif
|
|
118
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
|
|
156
|
+
_exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
119
159
|
/* The message goes out through `nish_write_all`, below, which finishes a
|
|
120
160
|
short write and retries one a signal interrupts (docs/security/runtime.md,
|
|
121
161
|
RT-11); it is unbuffered, so the message leaves before `_exit`. It used
|
|
@@ -124,7 +164,7 @@ static void nish_write_all(int fd, const char *p, uint64_t n);
|
|
|
124
164
|
static NISH_COLD void nish_die(const char *msg) {
|
|
125
165
|
nish_panic_claim();
|
|
126
166
|
nish_write_all(2, msg, strlen(msg));
|
|
127
|
-
|
|
167
|
+
nish_fatal_exit();
|
|
128
168
|
}
|
|
129
169
|
/* A function rather than a macro around `nish_die`: seven call sites each
|
|
130
170
|
loading the message's address cost more than one more symbol does. */
|
|
@@ -136,7 +176,13 @@ static NISH_COLD void nish_oom(void) { nish_die("nish: out of memory\n"); }
|
|
|
136
176
|
|
|
137
177
|
/* Slow path (size 8-byte rounded): push a chunk (>= 64 KB), bump from it. */
|
|
138
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
|
|
139
184
|
uint64_t cap = size > 65536 ? size : 65536;
|
|
185
|
+
#endif
|
|
140
186
|
/* A 64-bit request cannot always be asked for: wasm32's `size_t` is 32 bits,
|
|
141
187
|
and malloc would silently take the low half of a chunk nobody can address. */
|
|
142
188
|
if (cap > (uint64_t)(SIZE_MAX - sizeof(nish_chunk))) nish_oom();
|
|
@@ -151,8 +197,22 @@ void *nish_arena_grow(uint64_t size) {
|
|
|
151
197
|
return nish_arena.buf;
|
|
152
198
|
}
|
|
153
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
|
+
|
|
154
211
|
/* Bump allocation, 8-byte rounded/aligned, uninitialised; the compiler inlines it. */
|
|
155
212
|
void *nish_alloc_struct(uint64_t size) {
|
|
213
|
+
#ifdef NISH_ASAN
|
|
214
|
+
return nish_asan_alloc(size);
|
|
215
|
+
#endif
|
|
156
216
|
size = (size + 7) & ~(uint64_t)7;
|
|
157
217
|
if (nish_arena.off + size <= nish_arena.cap) {
|
|
158
218
|
void *p = nish_arena.buf + nish_arena.off;
|
|
@@ -162,12 +222,91 @@ void *nish_alloc_struct(uint64_t size) {
|
|
|
162
222
|
return nish_arena_grow(size);
|
|
163
223
|
}
|
|
164
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
|
|
165
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
|
|
166
300
|
while (c != end) { nish_chunk *n = c->next; free(c); c = n; }
|
|
167
301
|
}
|
|
168
302
|
|
|
169
303
|
/* O(1) recycle: keep the newest chunk, free the rest. */
|
|
170
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
|
|
171
310
|
nish_chunk *keep = nish_arena.chunks;
|
|
172
311
|
if (!keep) return;
|
|
173
312
|
nish_free_until(keep->next, 0);
|
|
@@ -323,8 +462,9 @@ int64_t nish_str_index_of(const nish_str *s, const nish_str *sub) { return nish_
|
|
|
323
462
|
a write fails (docs/security/runtime.md, RT-11). A failure still ends the
|
|
324
463
|
output, `EAGAIN` on a non-blocking stdout included, since spinning on it
|
|
325
464
|
would burn a core. `EINTR` is retried, the newline's included: the only
|
|
326
|
-
|
|
327
|
-
|
|
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
|
|
328
468
|
arithmetic and out-of-memory panics (`nish_die`) are written here. The
|
|
329
469
|
index and slice panics and the file and entropy failures use `dprintf`, and
|
|
330
470
|
runtime-parallel.c's out of memory one `write`: neither retries `EINTR`. */
|
|
@@ -348,6 +488,9 @@ void nish_print(const nish_str *s) { nish_write(s, 1, 1); }
|
|
|
348
488
|
__attribute__((noreturn)) void nish_panic(const nish_str *s) {
|
|
349
489
|
nish_panic_claim();
|
|
350
490
|
nish_write(s, 2, 1);
|
|
491
|
+
#ifdef NISH_FUZZ_ABORT
|
|
492
|
+
abort();
|
|
493
|
+
#endif
|
|
351
494
|
exit(1);
|
|
352
495
|
}
|
|
353
496
|
|
|
@@ -1219,10 +1362,18 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
|
|
|
1219
1362
|
this first. */
|
|
1220
1363
|
nish_array *nish_argv;
|
|
1221
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
|
+
|
|
1222
1373
|
void nish_argv_init(int32_t argc, char **argv) {
|
|
1223
1374
|
nish_array *a = malloc(sizeof *a + argc * sizeof(nish_str *));
|
|
1224
|
-
nish_str **d = (nish_str **)(a + 1);
|
|
1225
1375
|
if (!a) nish_oom();
|
|
1376
|
+
nish_str **d = (nish_str **)(a + 1);
|
|
1226
1377
|
*a = (nish_array){ argc, argc, (char *)d };
|
|
1227
1378
|
nish_argv = a;
|
|
1228
1379
|
while (argc-- > 0) {
|
|
@@ -1273,13 +1424,46 @@ double nish_parse_number(const nish_str *s, int32_t mode) {
|
|
|
1273
1424
|
* line, every one of those rows runs fewer instructions than it did before, and
|
|
1274
1425
|
* the call costs nothing on a path that runs once per doubling. */
|
|
1275
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
|
|
1276
1444
|
uint64_t cap = a->cap ? a->cap * 2 : 4;
|
|
1445
|
+
#endif
|
|
1277
1446
|
if (cap > NISH_ARRAY_MAX) {
|
|
1278
1447
|
if (a->len >= NISH_ARRAY_MAX) nish_oom();
|
|
1279
1448
|
cap = NISH_ARRAY_MAX;
|
|
1280
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
|
|
1281
1455
|
char *data = nish_alloc_struct(cap * elem_size);
|
|
1456
|
+
#endif
|
|
1282
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
|
|
1283
1467
|
a->data = data;
|
|
1284
1468
|
a->cap = cap;
|
|
1285
1469
|
}
|
|
@@ -1301,7 +1485,7 @@ __attribute__((noinline)) void nish_wipe(nish_array *bytes) {
|
|
|
1301
1485
|
void nish_panic_index(uint64_t idx, uint64_t len) {
|
|
1302
1486
|
nish_panic_claim();
|
|
1303
1487
|
dprintf(2, "index out of range: %" PRIu64 " >= %" PRIu64 "\n", idx, len);
|
|
1304
|
-
|
|
1488
|
+
nish_fatal_exit();
|
|
1305
1489
|
}
|
|
1306
1490
|
|
|
1307
1491
|
/* `s.slice(start, end)` (WP15 section 4) with a range the string does not
|
|
@@ -1315,7 +1499,7 @@ void nish_panic_index(uint64_t idx, uint64_t len) {
|
|
|
1315
1499
|
void nish_panic_slice(int64_t start, int64_t end, int64_t len) {
|
|
1316
1500
|
nish_panic_claim();
|
|
1317
1501
|
dprintf(2, "slice out of range: [%" PRId64 ", %" PRId64 ") of length %" PRId64 "\n", start, end, len);
|
|
1318
|
-
|
|
1502
|
+
nish_fatal_exit();
|
|
1319
1503
|
}
|
|
1320
1504
|
|
|
1321
1505
|
/* ---- Checked arithmetic (Rust semantics): the failed-check paths. One table
|