@scriptc/runtime 0.0.4 → 0.0.5

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/src/scr_lib.c CHANGED
@@ -126,11 +126,25 @@ static void scr_lib_cleanup(void) {
126
126
  scr_versions_openssl_str = NULL;
127
127
  }
128
128
 
129
+ #ifndef SCR_LIB
130
+ /* Executable lane only: a library artifact has no argv and registers no
131
+ * atexit handlers (the emitted library init never calls this; keeping it out
132
+ * the archive's objects free of any atexit reference — the K8 ambient
133
+ * audit's bar). */
129
134
  void scr_lib_init(int argc, char **argv) {
130
135
  scr_lib_argc = argc;
131
136
  scr_lib_argv = argv;
132
137
  atexit(scr_lib_cleanup);
133
138
  }
139
+ #endif /* !SCR_LIB */
140
+
141
+ #ifdef SCR_LIB
142
+ /* Library builds never call scr_lib_init (a library artifact has no argv and registers no
143
+ * atexit handlers); the interned process values above still intern lazily
144
+ * on first read, so the library reset seam releases them here instead —
145
+ * scr_library_reset (scr_library.c) calls this every session reset. */
146
+ void scr_lib_session_cleanup(void) { scr_lib_cleanup(); }
147
+ #endif
134
148
 
135
149
  /* Raw argv accessors for the island's process shim (scr_island.c): the
136
150
  * island's process.argv must match the static world's ["scriptc",
@@ -261,8 +275,7 @@ ScrStr *scr_env_get(const ScrStr *name) {
261
275
  if (need == 0) return NULL; /* absent (an empty value still needs its NUL) */
262
276
  char *buf = malloc(need);
263
277
  if (!buf) {
264
- fputs("scriptc: out of memory\n", stderr);
265
- abort();
278
+ scr_trap("scriptc: out of memory\n");
266
279
  }
267
280
  DWORD got = GetEnvironmentVariableA(name->data, buf, need);
268
281
  ScrStr *s = scr_str_new(buf, got);
@@ -553,8 +566,7 @@ bool scr_process_kill_named(double pid, const ScrStr *signal) {
553
566
  size_t cap = 16 + signal->len + 1;
554
567
  char *msg = malloc(cap);
555
568
  if (!msg) {
556
- fputs("scriptc: out of memory\n", stderr);
557
- abort();
569
+ scr_trap("scriptc: out of memory\n");
558
570
  }
559
571
  int len = snprintf(msg, cap, "Unknown signal: %s", signal->data);
560
572
  scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
@@ -567,8 +579,7 @@ bool scr_process_kill_named(double pid, const ScrStr *signal) {
567
579
  ScrStr *scr_process_cwd(void) {
568
580
  char buf[4096];
569
581
  if (!getcwd(buf, sizeof buf)) {
570
- fputs("scriptc: process.cwd() failed\n", stderr);
571
- abort();
582
+ scr_trap("scriptc: process.cwd() failed\n");
572
583
  }
573
584
  return scr_str_new(buf, strlen(buf));
574
585
  }
@@ -612,16 +623,14 @@ bool scr_proc_stream_write(double fd, const ScrStr *data) {
612
623
  ScrStr *scr_os_homedir(void) {
613
624
  const char *home = getenv("USERPROFILE");
614
625
  if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
615
- fputs("scriptc: os.homedir() failed\n", stderr);
616
- abort();
626
+ scr_trap("scriptc: os.homedir() failed\n");
617
627
  }
618
628
 
619
629
  ScrStr *scr_os_user_name(void) {
620
630
  char buf[UNLEN + 1];
621
631
  DWORD n = sizeof buf;
622
632
  if (!GetUserNameA(buf, &n) || n == 0) {
623
- fputs("scriptc: os.userInfo() failed\n", stderr);
624
- abort();
633
+ scr_trap("scriptc: os.userInfo() failed\n");
625
634
  }
626
635
  return scr_str_new(buf, n - 1); /* n counts the NUL */
627
636
  }
@@ -648,8 +657,7 @@ ScrStr *scr_os_release(void) {
648
657
  RtlGetVersionFn fn =
649
658
  ntdll ? (RtlGetVersionFn)(void *)GetProcAddress(ntdll, "RtlGetVersion") : NULL;
650
659
  if (fn == NULL || fn(&info) != 0) {
651
- fputs("scriptc: os.release() failed\n", stderr);
652
- abort();
660
+ scr_trap("scriptc: os.release() failed\n");
653
661
  }
654
662
  char buf[64];
655
663
  int len = snprintf(buf, sizeof buf, "%lu.%lu.%lu", (unsigned long)info.dwMajorVersion,
@@ -676,8 +684,7 @@ ScrStr *scr_os_tmpdir(void) {
676
684
  char buf[MAX_PATH + 2];
677
685
  DWORD n = GetTempPathA(sizeof buf, buf);
678
686
  if (n == 0 || n >= sizeof buf) {
679
- fputs("scriptc: os.tmpdir() failed\n", stderr);
680
- abort();
687
+ scr_trap("scriptc: os.tmpdir() failed\n");
681
688
  }
682
689
  size_t len = n;
683
690
  if (len > 1 && (buf[len - 1] == '\\' || buf[len - 1] == '/')) len--;
@@ -693,8 +700,7 @@ ScrStr *scr_os_homedir(void) {
693
700
  struct passwd *result = NULL;
694
701
  char buf[8192];
695
702
  if (getpwuid_r(getuid(), &pw, buf, sizeof buf, &result) != 0 || !result || !result->pw_dir) {
696
- fputs("scriptc: os.homedir() failed\n", stderr);
697
- abort();
703
+ scr_trap("scriptc: os.homedir() failed\n");
698
704
  }
699
705
  return scr_str_new(result->pw_dir, strlen(result->pw_dir));
700
706
  }
@@ -706,8 +712,7 @@ ScrStr *scr_os_homedir(void) {
706
712
  static const struct passwd *scr_os_passwd(char *buf, size_t cap, struct passwd *pw) {
707
713
  struct passwd *result = NULL;
708
714
  if (getpwuid_r(getuid(), pw, buf, cap, &result) != 0 || !result) {
709
- fputs("scriptc: os.userInfo() failed\n", stderr);
710
- abort();
715
+ scr_trap("scriptc: os.userInfo() failed\n");
711
716
  }
712
717
  return result;
713
718
  }
@@ -740,8 +745,7 @@ ScrStr *scr_os_release(void) {
740
745
  /* uname(2)'s release field — Node's uv_os_uname()-backed answer. */
741
746
  struct utsname u;
742
747
  if (uname(&u) != 0) {
743
- fputs("scriptc: os.release() failed\n", stderr);
744
- abort();
748
+ scr_trap("scriptc: os.release() failed\n");
745
749
  }
746
750
  return scr_str_new(u.release, strlen(u.release));
747
751
  }
@@ -750,8 +754,7 @@ ScrStr *scr_os_type(void) {
750
754
  /* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */
751
755
  struct utsname u;
752
756
  if (uname(&u) != 0) {
753
- fputs("scriptc: os.type() failed\n", stderr);
754
- abort();
757
+ scr_trap("scriptc: os.type() failed\n");
755
758
  }
756
759
  return scr_str_new(u.sysname, strlen(u.sysname));
757
760
  }
@@ -824,20 +827,20 @@ struct ScrIfaddrs {
824
827
  * stance. */
825
828
  ScrIfaddrs *scr_os_ifaddrs(void) {
826
829
  ScrIfaddrs *s = calloc(1, sizeof *s);
827
- if (!s) abort();
830
+ if (!s) scr_trap("scriptc: out of memory\n");
828
831
  ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
829
832
  GAA_FLAG_SKIP_DNS_SERVER;
830
833
  ULONG size = 16 * 1024;
831
834
  IP_ADAPTER_ADDRESSES *adapters = NULL;
832
835
  for (int tries = 0; tries < 4; tries++) {
833
836
  adapters = realloc(adapters, size);
834
- if (!adapters) abort();
837
+ if (!adapters) scr_trap("scriptc: out of memory\n");
835
838
  ULONG rc = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
836
839
  if (rc == ERROR_SUCCESS) break;
837
840
  if (rc != ERROR_BUFFER_OVERFLOW) {
838
841
  free(adapters);
839
842
  s->rows = calloc(1, sizeof *s->rows);
840
- if (!s->rows) abort();
843
+ if (!s->rows) scr_trap("scriptc: out of memory\n");
841
844
  return s; /* empty snapshot */
842
845
  }
843
846
  }
@@ -850,7 +853,7 @@ ScrIfaddrs *scr_os_ifaddrs(void) {
850
853
  }
851
854
  }
852
855
  s->rows = calloc(count ? count : 1, sizeof *s->rows);
853
- if (!s->rows) abort();
856
+ if (!s->rows) scr_trap("scriptc: out of memory\n");
854
857
  for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
855
858
  if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
856
859
  char name[64] = "";
@@ -934,7 +937,7 @@ static int scr_netmask_prefix(const unsigned char *bytes, size_t len) {
934
937
 
935
938
  ScrIfaddrs *scr_os_ifaddrs(void) {
936
939
  ScrIfaddrs *s = calloc(1, sizeof *s);
937
- if (!s) abort();
940
+ if (!s) scr_trap("scriptc: out of memory\n");
938
941
  struct ifaddrs *addrs = NULL;
939
942
  if (getifaddrs(&addrs) != 0) return s;
940
943
  size_t count = 0;
@@ -942,7 +945,7 @@ ScrIfaddrs *scr_os_ifaddrs(void) {
942
945
  if (scr_ifaddr_row_ok(ent)) count++;
943
946
  }
944
947
  s->rows = calloc(count ? count : 1, sizeof *s->rows);
945
- if (!s->rows) abort();
948
+ if (!s->rows) scr_trap("scriptc: out of memory\n");
946
949
  for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
947
950
  if (!scr_ifaddr_row_ok(ent)) continue;
948
951
  ScrIfaddrRow *row = &s->rows[s->n++];
@@ -1419,8 +1422,7 @@ void scr_fs_throw(int e, const char *op, const ScrStr *path) {
1419
1422
  size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
1420
1423
  char *msg = malloc(cap);
1421
1424
  if (!msg) {
1422
- fputs("scriptc: out of memory\n", stderr);
1423
- abort();
1425
+ scr_trap("scriptc: out of memory\n");
1424
1426
  }
1425
1427
  int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
1426
1428
  /* A real Error instance (name "Error", message = Node's text) — what a
@@ -1443,16 +1445,14 @@ ScrStr *scr_fs_read_file(ScrStr *path) {
1443
1445
  size_t cap = 4096, len = 0;
1444
1446
  char *buf = malloc(cap);
1445
1447
  if (!buf) {
1446
- fputs("scriptc: out of memory\n", stderr);
1447
- abort();
1448
+ scr_trap("scriptc: out of memory\n");
1448
1449
  }
1449
1450
  for (;;) {
1450
1451
  if (cap - len < 2048) {
1451
1452
  cap *= 2;
1452
1453
  char *grown = realloc(buf, cap);
1453
1454
  if (!grown) {
1454
- fputs("scriptc: out of memory\n", stderr);
1455
- abort();
1455
+ scr_trap("scriptc: out of memory\n");
1456
1456
  }
1457
1457
  buf = grown;
1458
1458
  }
@@ -1698,8 +1698,7 @@ static void scr_fs_throw2(int e, const char *op, const ScrStr *src, const ScrStr
1698
1698
  size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown_src) + strlen(shown_dest) + 16;
1699
1699
  char *msg = malloc(cap);
1700
1700
  if (!msg) {
1701
- fputs("scriptc: out of memory\n", stderr);
1702
- abort();
1701
+ scr_trap("scriptc: out of memory\n");
1703
1702
  }
1704
1703
  int len = snprintf(msg, cap, "%s: %s, %s '%s' -> '%s'", name, text, op, shown_src, shown_dest);
1705
1704
  scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
@@ -1856,8 +1855,7 @@ static void scr_mkdir_rec(char *path, size_t len, mode_t mode) {
1856
1855
  void scr_fs_mkdir_recursive(ScrStr *path) {
1857
1856
  char *buf = malloc(path->len + 1);
1858
1857
  if (!buf) {
1859
- fputs("scriptc: out of memory\n", stderr);
1860
- abort();
1858
+ scr_trap("scriptc: out of memory\n");
1861
1859
  }
1862
1860
  memcpy(buf, path->data, path->len + 1); /* ScrStr data is NUL-terminated */
1863
1861
  scr_mkdir_rec(buf, path->len, 0777);
@@ -1869,8 +1867,7 @@ void scr_fs_mkdir_recursive(ScrStr *path) {
1869
1867
  void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode) {
1870
1868
  char *buf = malloc(path->len + 1);
1871
1869
  if (!buf) {
1872
- fputs("scriptc: out of memory\n", stderr);
1873
- abort();
1870
+ scr_trap("scriptc: out of memory\n");
1874
1871
  }
1875
1872
  memcpy(buf, path->data, path->len + 1);
1876
1873
  scr_mkdir_rec(buf, path->len, (mode_t)mode);
@@ -1917,8 +1914,7 @@ static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
1917
1914
  size_t namelen = strlen(ent->d_name);
1918
1915
  char *child = malloc(len + 1 + namelen + 1);
1919
1916
  if (!child) {
1920
- fputs("scriptc: out of memory\n", stderr);
1921
- abort();
1917
+ scr_trap("scriptc: out of memory\n");
1922
1918
  }
1923
1919
  memcpy(child, path, len);
1924
1920
  child[len] = '/';
@@ -2021,8 +2017,7 @@ static char *scr_win_mkdtemp(char *tmpl) {
2021
2017
  ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
2022
2018
  char *tmpl = malloc(prefix->len + 7);
2023
2019
  if (!tmpl) {
2024
- fputs("scriptc: out of memory\n", stderr);
2025
- abort();
2020
+ scr_trap("scriptc: out of memory\n");
2026
2021
  }
2027
2022
  memcpy(tmpl, prefix->data, prefix->len);
2028
2023
  memcpy(tmpl + prefix->len, "XXXXXX", 7);
@@ -2069,16 +2064,14 @@ static char *scr_read_fd_all(double fd, size_t *out_len) {
2069
2064
  size_t cap = 4096, len = 0;
2070
2065
  char *buf = malloc(cap);
2071
2066
  if (!buf) {
2072
- fputs("scriptc: out of memory\n", stderr);
2073
- abort();
2067
+ scr_trap("scriptc: out of memory\n");
2074
2068
  }
2075
2069
  for (;;) {
2076
2070
  if (cap - len < 2048) {
2077
2071
  cap *= 2;
2078
2072
  char *grown = realloc(buf, cap);
2079
2073
  if (!grown) {
2080
- fputs("scriptc: out of memory\n", stderr);
2081
- abort();
2074
+ scr_trap("scriptc: out of memory\n");
2082
2075
  }
2083
2076
  buf = grown;
2084
2077
  }
@@ -2286,8 +2279,7 @@ double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
2286
2279
  static ScrStats *scr_stats_of(const struct stat *st) {
2287
2280
  ScrStats *s = malloc(sizeof(ScrStats));
2288
2281
  if (!s) {
2289
- fputs("scriptc: out of memory\n", stderr);
2290
- abort();
2282
+ scr_trap("scriptc: out of memory\n");
2291
2283
  }
2292
2284
  s->rc = 1;
2293
2285
  s->is_file = S_ISREG(st->st_mode);
@@ -2384,16 +2376,14 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
2384
2376
  }
2385
2377
  ScrScandir *s = malloc(sizeof *s);
2386
2378
  if (!s) {
2387
- fputs("scriptc: out of memory\n", stderr);
2388
- abort();
2379
+ scr_trap("scriptc: out of memory\n");
2389
2380
  }
2390
2381
  s->len = 0;
2391
2382
  s->cap = 8;
2392
2383
  s->names = malloc(s->cap * sizeof *s->names);
2393
2384
  s->kinds = malloc(s->cap);
2394
2385
  if (!s->names || !s->kinds) {
2395
- fputs("scriptc: out of memory\n", stderr);
2396
- abort();
2386
+ scr_trap("scriptc: out of memory\n");
2397
2387
  }
2398
2388
  const struct dirent *ent;
2399
2389
  while ((ent = readdir(d)) != NULL) {
@@ -2434,8 +2424,7 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
2434
2424
  s->names = realloc(s->names, s->cap * sizeof *s->names);
2435
2425
  s->kinds = realloc(s->kinds, s->cap);
2436
2426
  if (!s->names || !s->kinds) {
2437
- fputs("scriptc: out of memory\n", stderr);
2438
- abort();
2427
+ scr_trap("scriptc: out of memory\n");
2439
2428
  }
2440
2429
  }
2441
2430
  s->names[s->len] = scr_str_new(ent->d_name, strlen(ent->d_name));
@@ -2545,8 +2534,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2545
2534
  size_t size = (size_t)n;
2546
2535
  unsigned char *bytes = malloc(size ? size : 1);
2547
2536
  if (!bytes) {
2548
- fputs("scriptc: out of memory\n", stderr);
2549
- abort();
2537
+ scr_trap("scriptc: out of memory\n");
2550
2538
  }
2551
2539
  arc4random_buf(bytes, size);
2552
2540
  ScrStr *out;
@@ -2554,8 +2542,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2554
2542
  static const char hex[] = "0123456789abcdef";
2555
2543
  char *buf = malloc(size * 2 + 1);
2556
2544
  if (!buf) {
2557
- fputs("scriptc: out of memory\n", stderr);
2558
- abort();
2545
+ scr_trap("scriptc: out of memory\n");
2559
2546
  }
2560
2547
  for (size_t i = 0; i < size; i++) {
2561
2548
  buf[i * 2] = hex[bytes[i] >> 4];
@@ -2569,8 +2556,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2569
2556
  size_t outlen = (size + 2) / 3 * 4;
2570
2557
  char *buf = malloc(outlen + 1);
2571
2558
  if (!buf) {
2572
- fputs("scriptc: out of memory\n", stderr);
2573
- abort();
2559
+ scr_trap("scriptc: out of memory\n");
2574
2560
  }
2575
2561
  size_t o = 0;
2576
2562
  for (size_t i = 0; i < size; i += 3) {
@@ -2909,8 +2895,7 @@ static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_
2909
2895
  }
2910
2896
  uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3);
2911
2897
  if (!out) {
2912
- fputs("scriptc: out of memory\n", stderr);
2913
- abort();
2898
+ scr_trap("scriptc: out of memory\n");
2914
2899
  }
2915
2900
  size_t o = 0;
2916
2901
  unsigned acc = 0;
@@ -3126,8 +3111,7 @@ static uint32_t scr_to_uint32(double d);
3126
3111
  static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) {
3127
3112
  char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */
3128
3113
  if (!out) {
3129
- fputs("scriptc: out of memory\n", stderr);
3130
- abort();
3114
+ scr_trap("scriptc: out of memory\n");
3131
3115
  }
3132
3116
  size_t o = 0;
3133
3117
  for (size_t i = 0; i < n; i++) {
@@ -0,0 +1,222 @@
1
+ /* Library mode support: the panic sink, the poisoned-library flag, the
2
+ * outbound-result arena, the reset registry, and the sink-routing trap
3
+ * funnel. Linked ONLY into library artifacts (every TU of a library archive
4
+ * compiles with -DSCR_LIB); executable builds never contain this file —
5
+ * their funnel expansion lives in scr_console.c and their session teardown
6
+ * stays atexit-driven.
7
+ *
8
+ * The profile-NAMED symbols (init, sink registration, reset, collect) are
9
+ * emitted in the program TU and delegate here, so this file stays
10
+ * profile-agnostic and the program TU carries every profile-named symbol —
11
+ * one place for the conformance symbol audit to look.
12
+ *
13
+ * State after a sink call: none is legal. The trap fired mid-operation with
14
+ * no unwinding — heap, arena, and collector state are unspecified; the library
15
+ * is POISONED. Every runtime-touching entry prologue aborts on the flag
16
+ * (deterministic, never heap corruption); recovery is process restart. */
17
+ #include "scr_runtime.h"
18
+
19
+ #ifdef SCR_LIB
20
+
21
+ #include <stdio.h>
22
+ #include <stdlib.h>
23
+
24
+ /* ── sink registration + poison ───────────────────────────────────────── */
25
+
26
+ static ScrLibSinkFn scr_library_sink = NULL;
27
+ static void *scr_library_sink_ctx = NULL;
28
+ static bool scr_library_poisoned = false;
29
+
30
+ void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
31
+ /* Latest registration wins; re-registration is permitted before a trap.
32
+ * Deliberately NOT poison-guarded: a pure store, touching no runtime
33
+ * state — but a poisoned library's entries abort regardless of the sink. */
34
+ scr_library_sink = fn;
35
+ scr_library_sink_ctx = ctx;
36
+ }
37
+
38
+ /* ── the trap funnel, library expansion ───────────────────────────────────
39
+ * Poison first (the sink may longjmp to a host frame below the entry — the
40
+ * conforming survival pattern), then deliver exactly once, then abort:
41
+ * before registration, or if the sink returns (the ruled host-contract
42
+ * violation). The address is the funnel frame's return address — the trap
43
+ * site — 0 where the toolchain cannot supply one. */
44
+
45
+ #if defined(__GNUC__) || defined(__clang__)
46
+ #define SCR_TRAP_ADDR() ((uint64_t)(uintptr_t)__builtin_return_address(0))
47
+ #else
48
+ #define SCR_TRAP_ADDR() ((uint64_t)0)
49
+ #endif
50
+
51
+ static _Noreturn void scr_library_trap_deliver(const char *msg, size_t len, uint64_t addr) {
52
+ scr_library_poisoned = true;
53
+ if (scr_library_sink != NULL) {
54
+ scr_library_sink(scr_library_sink_ctx, (const uint8_t *)msg, len, addr);
55
+ }
56
+ abort();
57
+ }
58
+
59
+ __attribute__((noinline)) _Noreturn void scr_trap(const char *msg) {
60
+ scr_library_trap_deliver(msg, strlen(msg), SCR_TRAP_ADDR());
61
+ }
62
+
63
+ __attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
64
+ static char buf[512]; /* no malloc on the invariant-failure path */
65
+ va_list ap;
66
+ va_start(ap, fmt);
67
+ int n = vsnprintf(buf, sizeof buf, fmt, ap);
68
+ va_end(ap);
69
+ size_t len = n < 0 ? 0 : (size_t)n >= sizeof buf ? sizeof buf - 1 : (size_t)n;
70
+ scr_library_trap_deliver(buf, len, SCR_TRAP_ADDR());
71
+ }
72
+
73
+ /* ── entry prologues ──────────────────────────────────────────────────── */
74
+
75
+ void scr_library_entry(bool reset_arena) {
76
+ /* A poisoned library's entries abort deterministically — never through the
77
+ * sink again (it received its exactly-once message when the trap fired),
78
+ * never into a heap whose invariants already failed. */
79
+ if (scr_library_poisoned) abort();
80
+ if (reset_arena) scr_library_arena_reset();
81
+ }
82
+
83
+ /* ── the outbound-result arena ────────────────────────────────────────────
84
+ * Buffer-class results (string/bytes) MOVE in here; the host reads through
85
+ * borrowed pointers until the arena resets (per-entry under the auto
86
+ * posture, host-cycled when the profile declares a reset symbol; init and
87
+ * collect always reset). */
88
+
89
+ typedef struct {
90
+ void *v;
91
+ bool is_str; /* ScrStr vs ScrBytes — picks the release */
92
+ } ScrCoreArenaEnt;
93
+
94
+ static ScrCoreArenaEnt *scr_library_arena = NULL;
95
+ static size_t scr_library_arena_n = 0, scr_library_arena_cap = 0;
96
+
97
+ static void scr_library_arena_keep(void *v, bool is_str) {
98
+ if (scr_library_arena_n == scr_library_arena_cap) {
99
+ scr_library_arena_cap = scr_library_arena_cap ? scr_library_arena_cap * 2 : 16;
100
+ scr_library_arena = realloc(scr_library_arena, scr_library_arena_cap * sizeof *scr_library_arena);
101
+ if (!scr_library_arena) scr_trap("scriptc: out of memory\n");
102
+ }
103
+ scr_library_arena[scr_library_arena_n].v = v;
104
+ scr_library_arena[scr_library_arena_n].is_str = is_str;
105
+ scr_library_arena_n++;
106
+ }
107
+
108
+ void scr_library_arena_reset(void) {
109
+ for (size_t i = 0; i < scr_library_arena_n; i++) {
110
+ if (scr_library_arena[i].is_str) scr_str_release((ScrStr *)scr_library_arena[i].v);
111
+ else scr_bytes_release((ScrBytes *)scr_library_arena[i].v);
112
+ }
113
+ scr_library_arena_n = 0;
114
+ }
115
+
116
+ void scr_library_collect(void) {
117
+ scr_library_arena_reset();
118
+ scr_collect_cycles();
119
+ }
120
+
121
+ /* ── marshalling helpers (both emissions call exactly these) ──────────── */
122
+
123
+ ScrStr *scr_library_str_in(const uint8_t *p, size_t len) {
124
+ /* ptr may be NULL when len is 0 (the contract's empty-buffer form). */
125
+ return scr_str_new(len == 0 ? "" : (const char *)p, len);
126
+ }
127
+
128
+ ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len) {
129
+ ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
130
+ if (b == NULL) {
131
+ /* scr_bytes_new throws only for lengths past 2^53-1 — an impossible
132
+ * host buffer; funnel the contract violation instead of a NULL deref. */
133
+ scr_exc_clear();
134
+ scr_trap("scriptc: library inbound bytes length out of range\n");
135
+ }
136
+ if (len > 0 && p != NULL) memcpy(b->data, p, len);
137
+ return b;
138
+ }
139
+
140
+ void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len) {
141
+ scr_library_arena_keep(s, true);
142
+ *out = (const uint8_t *)s->data; /* NUL-terminated after len (ScrStr layout) */
143
+ *out_len = s->len;
144
+ }
145
+
146
+ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len) {
147
+ scr_library_arena_keep(b, false);
148
+ *out = b->data; /* elem is always u8 at the boundary (v1 bytes class) */
149
+ *out_len = b->len;
150
+ }
151
+
152
+ /* ── the reset registry + session reset ─────────────────────────────────
153
+ * Where an executable's always-linked units atexit() their lazy teardowns,
154
+ * library builds register here (scr_atexit in scr_runtime.h): registered
155
+ * once, drained on EVERY reset — re-registration guards in the units stay
156
+ * satisfied because the entry persists. */
157
+
158
+ #define SCR_LIB_MAX_RESETS 32
159
+ static void (*scr_library_resets[SCR_LIB_MAX_RESETS])(void);
160
+ static size_t scr_library_nresets = 0;
161
+
162
+ void scr_library_register_reset(void (*fn)(void)) {
163
+ for (size_t i = 0; i < scr_library_nresets; i++) {
164
+ if (scr_library_resets[i] == fn) return;
165
+ }
166
+ if (scr_library_nresets == SCR_LIB_MAX_RESETS) {
167
+ scr_trap("scriptc: internal error: library reset registry overflow\n");
168
+ }
169
+ scr_library_resets[scr_library_nresets++] = fn;
170
+ }
171
+
172
+ #ifdef SCR_RC_AUDIT
173
+ extern long scr_str_live_count(void); /* scr_string.c */
174
+ extern long scr_arr_live_count(void); /* scr_array.c */
175
+ extern long scr_map_live_count(void); /* scr_map.c */
176
+ extern long scr_box_live_count(void); /* scr_closure.c */
177
+ extern long scr_closure_live_count(void); /* scr_closure.c */
178
+ extern long scr_obj_live_count(void); /* scr_object.c */
179
+ extern long scr_union_live_count(void); /* scr_union.c */
180
+ extern long scr_dyn_live_count(void); /* scr_json.c */
181
+ extern long scr_bytes_live_count(void); /* scr_bytes.c */
182
+
183
+ /* The per-session heap-emptiness assertion (the audit flavor's determinism
184
+ * seam): after a full reset the live counters must all read zero, or the
185
+ * previous session leaked. A failure is a TRAP through the sink — the
186
+ * executable audit's _Exit(99) stays exe-lane-only. */
187
+ static void scr_library_audit_zero(void) {
188
+ long strings = scr_str_live_count(), arrays = scr_arr_live_count(),
189
+ maps = scr_map_live_count(), boxes = scr_box_live_count(),
190
+ closures = scr_closure_live_count(), objects = scr_obj_live_count(),
191
+ unions = scr_union_live_count(), dyns = scr_dyn_live_count(),
192
+ bytes = scr_bytes_live_count();
193
+ if (strings != 0 || arrays != 0 || maps != 0 || boxes != 0 || closures != 0 ||
194
+ objects != 0 || unions != 0 || dyns != 0 || bytes != 0) {
195
+ scr_trap_fmt(
196
+ "scriptc LIBRARY RC AUDIT FAILED: %ld heap string(s), %ld array(s), "
197
+ "%ld map(s), %ld box(es), %ld closure(s), %ld object(s), "
198
+ "%ld union(s), %ld dyn value(s), %ld bytes value(s) live across re-init\n",
199
+ strings, arrays, maps, boxes, closures, objects, unions, dyns, bytes);
200
+ }
201
+ }
202
+ #endif /* SCR_RC_AUDIT */
203
+
204
+ extern void scr_lib_session_cleanup(void); /* scr_lib.c: the interned process values */
205
+
206
+ void scr_library_reset(void) {
207
+ /* Called by the generated init entry AFTER the program TU released and
208
+ * zeroed its globals (run-once guards included) — the same order the
209
+ * executable exit path runs: library cleanup → cycle collection → RC
210
+ * audit. Everything here is re-runnable; the first call is a cheap
211
+ * no-op pass. */
212
+ scr_exc_clear();
213
+ scr_library_arena_reset();
214
+ for (size_t i = 0; i < scr_library_nresets; i++) scr_library_resets[i]();
215
+ scr_lib_session_cleanup();
216
+ scr_collect_cycles();
217
+ #ifdef SCR_RC_AUDIT
218
+ scr_library_audit_zero();
219
+ #endif
220
+ }
221
+
222
+ #endif /* SCR_LIB */
package/src/scr_map.c CHANGED
@@ -31,8 +31,7 @@ long scr_map_live_count(void) { return scr_live_maps; }
31
31
  #define SCR_MAP_EMPTY SIZE_MAX
32
32
 
33
33
  static void scr_map_oom(void) {
34
- fputs("scriptc: out of memory\n", stderr);
35
- abort();
34
+ scr_trap("scriptc: out of memory\n");
36
35
  }
37
36
 
38
37
  /* ── key normalization + hashing (SameValueZero) ───────────────────────
@@ -462,8 +461,7 @@ bool scr_map_iter_live(const ScrMap *m, double i) {
462
461
 
463
462
  static const ScrMapEntry *scr_map_iter_at(const ScrMap *m, double i) {
464
463
  if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].live) {
465
- fputs("scriptc: internal error: map iteration index out of range\n", stderr);
466
- abort();
464
+ scr_trap("scriptc: internal error: map iteration index out of range\n");
467
465
  }
468
466
  return &m->entries[(size_t)i];
469
467
  }
package/src/scr_path.c CHANGED
@@ -37,8 +37,7 @@ static void pb_init(PathBuf *b) {
37
37
  b->len = 0;
38
38
  b->data = malloc(b->cap);
39
39
  if (!b->data) {
40
- fputs("scriptc: out of memory\n", stderr);
41
- abort();
40
+ scr_trap("scriptc: out of memory\n");
42
41
  }
43
42
  }
44
43
 
@@ -47,8 +46,7 @@ static void pb_reserve(PathBuf *b, size_t extra) {
47
46
  while (b->len + extra > b->cap) b->cap *= 2;
48
47
  char *grown = realloc(b->data, b->cap);
49
48
  if (!grown) {
50
- fputs("scriptc: out of memory\n", stderr);
51
- abort();
49
+ scr_trap("scriptc: out of memory\n");
52
50
  }
53
51
  b->data = grown;
54
52
  }
@@ -226,8 +224,7 @@ ScrStr *scr_path_join(ScrArr *parts) {
226
224
  static void scr_path_cwd(PathBuf *out) {
227
225
  char buf[4096];
228
226
  if (!getcwd(buf, sizeof buf)) {
229
- fputs("scriptc: path.resolve: getcwd failed\n", stderr);
230
- abort();
227
+ scr_trap("scriptc: path.resolve: getcwd failed\n");
231
228
  }
232
229
  pb_append(out, buf, strlen(buf));
233
230
  }
@@ -984,8 +981,7 @@ ScrStr *scr_path_win32_relative(ScrStr *from, ScrStr *to) {
984
981
  char *flow = malloc(rfrom->len ? rfrom->len : 1);
985
982
  char *tlow = malloc(rto->len ? rto->len : 1);
986
983
  if (!flow || !tlow) {
987
- fputs("scriptc: out of memory\n", stderr);
988
- abort();
984
+ scr_trap("scriptc: out of memory\n");
989
985
  }
990
986
  for (size_t k = 0; k < rfrom->len; k++) flow[k] = scr_path_w32_lower(rfrom->data[k]);
991
987
  for (size_t k = 0; k < rto->len; k++) tlow[k] = scr_path_w32_lower(rto->data[k]);