@scriptc/runtime 0.0.3 → 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++];
@@ -1093,6 +1096,16 @@ double scr_process_uptime(void) {
1093
1096
  return (scr_uptime_now_ms() - scr_uptime_t0_ms) / 1000.0;
1094
1097
  }
1095
1098
 
1099
+ /* perf_hooks performance.now(): milliseconds since the process's own
1100
+ * start (Node's timeOrigin anchor), fractional — the same monotonic
1101
+ * clock and anchor as uptime, in Node's performance.now units. */
1102
+ double scr_perf_now(void) {
1103
+ #ifdef _WIN32
1104
+ if (scr_uptime_t0_ms == 0) scr_uptime_anchor_init();
1105
+ #endif
1106
+ return scr_uptime_now_ms() - scr_uptime_t0_ms;
1107
+ }
1108
+
1096
1109
  #ifdef _WIN32
1097
1110
  /* GetProcessTimes/GetThreadTimes answer 100ns units; Node reports µs. */
1098
1111
  static double scr_filetime_us(FILETIME ft) {
@@ -1409,8 +1422,7 @@ void scr_fs_throw(int e, const char *op, const ScrStr *path) {
1409
1422
  size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
1410
1423
  char *msg = malloc(cap);
1411
1424
  if (!msg) {
1412
- fputs("scriptc: out of memory\n", stderr);
1413
- abort();
1425
+ scr_trap("scriptc: out of memory\n");
1414
1426
  }
1415
1427
  int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
1416
1428
  /* A real Error instance (name "Error", message = Node's text) — what a
@@ -1433,16 +1445,14 @@ ScrStr *scr_fs_read_file(ScrStr *path) {
1433
1445
  size_t cap = 4096, len = 0;
1434
1446
  char *buf = malloc(cap);
1435
1447
  if (!buf) {
1436
- fputs("scriptc: out of memory\n", stderr);
1437
- abort();
1448
+ scr_trap("scriptc: out of memory\n");
1438
1449
  }
1439
1450
  for (;;) {
1440
1451
  if (cap - len < 2048) {
1441
1452
  cap *= 2;
1442
1453
  char *grown = realloc(buf, cap);
1443
1454
  if (!grown) {
1444
- fputs("scriptc: out of memory\n", stderr);
1445
- abort();
1455
+ scr_trap("scriptc: out of memory\n");
1446
1456
  }
1447
1457
  buf = grown;
1448
1458
  }
@@ -1688,8 +1698,7 @@ static void scr_fs_throw2(int e, const char *op, const ScrStr *src, const ScrStr
1688
1698
  size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown_src) + strlen(shown_dest) + 16;
1689
1699
  char *msg = malloc(cap);
1690
1700
  if (!msg) {
1691
- fputs("scriptc: out of memory\n", stderr);
1692
- abort();
1701
+ scr_trap("scriptc: out of memory\n");
1693
1702
  }
1694
1703
  int len = snprintf(msg, cap, "%s: %s, %s '%s' -> '%s'", name, text, op, shown_src, shown_dest);
1695
1704
  scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
@@ -1846,8 +1855,7 @@ static void scr_mkdir_rec(char *path, size_t len, mode_t mode) {
1846
1855
  void scr_fs_mkdir_recursive(ScrStr *path) {
1847
1856
  char *buf = malloc(path->len + 1);
1848
1857
  if (!buf) {
1849
- fputs("scriptc: out of memory\n", stderr);
1850
- abort();
1858
+ scr_trap("scriptc: out of memory\n");
1851
1859
  }
1852
1860
  memcpy(buf, path->data, path->len + 1); /* ScrStr data is NUL-terminated */
1853
1861
  scr_mkdir_rec(buf, path->len, 0777);
@@ -1859,8 +1867,7 @@ void scr_fs_mkdir_recursive(ScrStr *path) {
1859
1867
  void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode) {
1860
1868
  char *buf = malloc(path->len + 1);
1861
1869
  if (!buf) {
1862
- fputs("scriptc: out of memory\n", stderr);
1863
- abort();
1870
+ scr_trap("scriptc: out of memory\n");
1864
1871
  }
1865
1872
  memcpy(buf, path->data, path->len + 1);
1866
1873
  scr_mkdir_rec(buf, path->len, (mode_t)mode);
@@ -1907,8 +1914,7 @@ static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
1907
1914
  size_t namelen = strlen(ent->d_name);
1908
1915
  char *child = malloc(len + 1 + namelen + 1);
1909
1916
  if (!child) {
1910
- fputs("scriptc: out of memory\n", stderr);
1911
- abort();
1917
+ scr_trap("scriptc: out of memory\n");
1912
1918
  }
1913
1919
  memcpy(child, path, len);
1914
1920
  child[len] = '/';
@@ -2011,8 +2017,7 @@ static char *scr_win_mkdtemp(char *tmpl) {
2011
2017
  ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
2012
2018
  char *tmpl = malloc(prefix->len + 7);
2013
2019
  if (!tmpl) {
2014
- fputs("scriptc: out of memory\n", stderr);
2015
- abort();
2020
+ scr_trap("scriptc: out of memory\n");
2016
2021
  }
2017
2022
  memcpy(tmpl, prefix->data, prefix->len);
2018
2023
  memcpy(tmpl + prefix->len, "XXXXXX", 7);
@@ -2059,16 +2064,14 @@ static char *scr_read_fd_all(double fd, size_t *out_len) {
2059
2064
  size_t cap = 4096, len = 0;
2060
2065
  char *buf = malloc(cap);
2061
2066
  if (!buf) {
2062
- fputs("scriptc: out of memory\n", stderr);
2063
- abort();
2067
+ scr_trap("scriptc: out of memory\n");
2064
2068
  }
2065
2069
  for (;;) {
2066
2070
  if (cap - len < 2048) {
2067
2071
  cap *= 2;
2068
2072
  char *grown = realloc(buf, cap);
2069
2073
  if (!grown) {
2070
- fputs("scriptc: out of memory\n", stderr);
2071
- abort();
2074
+ scr_trap("scriptc: out of memory\n");
2072
2075
  }
2073
2076
  buf = grown;
2074
2077
  }
@@ -2276,8 +2279,7 @@ double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
2276
2279
  static ScrStats *scr_stats_of(const struct stat *st) {
2277
2280
  ScrStats *s = malloc(sizeof(ScrStats));
2278
2281
  if (!s) {
2279
- fputs("scriptc: out of memory\n", stderr);
2280
- abort();
2282
+ scr_trap("scriptc: out of memory\n");
2281
2283
  }
2282
2284
  s->rc = 1;
2283
2285
  s->is_file = S_ISREG(st->st_mode);
@@ -2374,16 +2376,14 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
2374
2376
  }
2375
2377
  ScrScandir *s = malloc(sizeof *s);
2376
2378
  if (!s) {
2377
- fputs("scriptc: out of memory\n", stderr);
2378
- abort();
2379
+ scr_trap("scriptc: out of memory\n");
2379
2380
  }
2380
2381
  s->len = 0;
2381
2382
  s->cap = 8;
2382
2383
  s->names = malloc(s->cap * sizeof *s->names);
2383
2384
  s->kinds = malloc(s->cap);
2384
2385
  if (!s->names || !s->kinds) {
2385
- fputs("scriptc: out of memory\n", stderr);
2386
- abort();
2386
+ scr_trap("scriptc: out of memory\n");
2387
2387
  }
2388
2388
  const struct dirent *ent;
2389
2389
  while ((ent = readdir(d)) != NULL) {
@@ -2424,8 +2424,7 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
2424
2424
  s->names = realloc(s->names, s->cap * sizeof *s->names);
2425
2425
  s->kinds = realloc(s->kinds, s->cap);
2426
2426
  if (!s->names || !s->kinds) {
2427
- fputs("scriptc: out of memory\n", stderr);
2428
- abort();
2427
+ scr_trap("scriptc: out of memory\n");
2429
2428
  }
2430
2429
  }
2431
2430
  s->names[s->len] = scr_str_new(ent->d_name, strlen(ent->d_name));
@@ -2535,8 +2534,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2535
2534
  size_t size = (size_t)n;
2536
2535
  unsigned char *bytes = malloc(size ? size : 1);
2537
2536
  if (!bytes) {
2538
- fputs("scriptc: out of memory\n", stderr);
2539
- abort();
2537
+ scr_trap("scriptc: out of memory\n");
2540
2538
  }
2541
2539
  arc4random_buf(bytes, size);
2542
2540
  ScrStr *out;
@@ -2544,8 +2542,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2544
2542
  static const char hex[] = "0123456789abcdef";
2545
2543
  char *buf = malloc(size * 2 + 1);
2546
2544
  if (!buf) {
2547
- fputs("scriptc: out of memory\n", stderr);
2548
- abort();
2545
+ scr_trap("scriptc: out of memory\n");
2549
2546
  }
2550
2547
  for (size_t i = 0; i < size; i++) {
2551
2548
  buf[i * 2] = hex[bytes[i] >> 4];
@@ -2559,8 +2556,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
2559
2556
  size_t outlen = (size + 2) / 3 * 4;
2560
2557
  char *buf = malloc(outlen + 1);
2561
2558
  if (!buf) {
2562
- fputs("scriptc: out of memory\n", stderr);
2563
- abort();
2559
+ scr_trap("scriptc: out of memory\n");
2564
2560
  }
2565
2561
  size_t o = 0;
2566
2562
  for (size_t i = 0; i < size; i += 3) {
@@ -2899,8 +2895,7 @@ static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_
2899
2895
  }
2900
2896
  uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3);
2901
2897
  if (!out) {
2902
- fputs("scriptc: out of memory\n", stderr);
2903
- abort();
2898
+ scr_trap("scriptc: out of memory\n");
2904
2899
  }
2905
2900
  size_t o = 0;
2906
2901
  unsigned acc = 0;
@@ -3116,8 +3111,7 @@ static uint32_t scr_to_uint32(double d);
3116
3111
  static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) {
3117
3112
  char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */
3118
3113
  if (!out) {
3119
- fputs("scriptc: out of memory\n", stderr);
3120
- abort();
3114
+ scr_trap("scriptc: out of memory\n");
3121
3115
  }
3122
3116
  size_t o = 0;
3123
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]);