Trap funnel: scr_trap/scr_trap_fmt over every core-linkable trap site

- executable expansion in scr_console.c is byte-identical fputs/vfprintf + abort; core expansion in the new scr_core.c poisons, delivers to the registered sink exactly once, aborts pre-registration or if the sink returns
- 89 fputs/fprintf+abort pairs and 10 bare OOM aborts across the 20 core-linkable TUs now funnel; lazy atexit teardowns (json errdyn, regex bytecodes, symbol registry) reroute to the core reset registry via scr_atexit
- scr_core.c owns sink/poison/result-arena/reset-registry/marshalling helpers; scr_exception.c renders escaped exceptions as "Uncaught ..." into the funnel; scr_lib exposes its cleanup to the reset seam
This commit is contained in:
Chris Tate
2026-07-23 01:14:32 -05:00
parent 5d4171f548
commit 59274de9d7
23 changed files with 495 additions and 196 deletions
+6 -12
View File
@@ -13,8 +13,7 @@ long scr_arr_live_count(void) { return scr_live_arrays; }
#endif
static void scr_arr_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* JS would return undefined for an OOB read and create holes for a far OOB
@@ -23,10 +22,8 @@ static void scr_arr_oom(void) {
static void scr_arr_trap_oob(double i, size_t len) {
char buf[32];
scr_f64_to_str(i, buf);
fprintf(stderr,
"scriptc: RangeError: array index %s out of bounds (length %zu)\n",
buf, len);
abort();
scr_trap_fmt("scriptc: RangeError: array index %s out of bounds (length %zu)\n",
buf, len);
}
/* Validate i as an element index. limit is a->len for reads, a->len + 1 for
@@ -264,8 +261,7 @@ double scr_arr_push_ref(ScrArr *a, void *v) {
static uint64_t scr_arr_pop_slot(ScrArr *a) {
if (a->len == 0) {
fputs("scriptc: RangeError: pop() on an empty array\n", stderr);
abort();
scr_trap("scriptc: RangeError: pop() on an empty array\n");
}
return a->data[--a->len];
}
@@ -283,8 +279,7 @@ void *scr_arr_pop_ref(ScrArr *a) { return scr_slot_to_ptr(scr_arr_pop_slot(a));
* ownership moves out to the caller (no retain). */
static uint64_t scr_arr_shift_slot(ScrArr *a) {
if (a->len == 0) {
fputs("scriptc: internal error: shift() on an empty array\n", stderr);
abort();
scr_trap("scriptc: internal error: shift() on an empty array\n");
}
uint64_t s = a->data[0];
a->len--;
@@ -456,8 +451,7 @@ ScrStr *scr_arr_join(ScrArr *a, ScrStr *sep) {
case SCR_ELEM_BYTES:
case SCR_ELEM_REF:
/* The compiler rejects join on ref-element arrays (SC1090). */
fputs("scriptc: internal error: join on a ref-element array\n", stderr);
abort();
scr_trap("scriptc: internal error: join on a ref-element array\n");
}
}
ScrStr *out = scr_str_new(buf, len);
+1 -2
View File
@@ -32,8 +32,7 @@
#include <string.h>
static void scr_assert_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── a tiny append-only byte buffer for message assembly ─────────────── */
+3 -6
View File
@@ -17,8 +17,7 @@ long scr_bytes_live_count(void) { return scr_live_bytes; }
#endif
static void scr_bytes_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t scr_bytes_elem_size(ScrBytesElem elem) {
@@ -98,10 +97,8 @@ static size_t scr_bytes_check_index(const ScrBytes *b, double i) {
if (!(i >= 0) || i != trunc(i) || i >= (double)b->len) {
char buf[32];
scr_f64_to_str(i, buf);
fprintf(stderr,
"scriptc: RangeError: typed array index %s out of bounds (length %zu)\n",
buf, b->len);
abort();
scr_trap_fmt("scriptc: RangeError: typed array index %s out of bounds (length %zu)\n",
buf, b->len);
}
return (size_t)i;
}
+1 -2
View File
@@ -12,8 +12,7 @@
#include <string.h>
static void scr_bytes_io_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── fs (the Buffer forms of scr_lib.c's utf8 pair) ────────────────────── */
+1 -2
View File
@@ -146,8 +146,7 @@ void *scr_box_get_ref(ScrBox *b) {
case SCR_BOX_OBJ:
return b->obj_retain(p);
default:
fputs("scriptc: internal error: box_get_ref on a scalar box\n", stderr);
abort();
scr_trap("scriptc: internal error: box_get_ref on a scalar box\n");
}
}
+20
View File
@@ -14,6 +14,26 @@
static long scr_abandoned_fibers = 0;
void scr_note_abandoned_fibers(long n) { scr_abandoned_fibers = n; }
#ifndef SCR_CORE
/* The trap funnel's EXECUTABLE expansion: exactly the historical
* fputs/vfprintf-to-stderr + abort every trap site used to open-code — the
* default lane's bytes and behavior are unchanged by construction. Core
* builds (-DSCR_CORE) get the sink-routing definitions from scr_core.c
* instead; this pair compiles out there. */
_Noreturn void scr_trap(const char *msg) {
fputs(msg, stderr);
abort();
}
_Noreturn void scr_trap_fmt(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
abort();
}
#endif /* !SCR_CORE */
#ifdef SCR_RC_AUDIT
extern long scr_str_live_count(void); /* scr_string.c */
extern long scr_arr_live_count(void); /* scr_array.c */
+221
View File
@@ -0,0 +1,221 @@
/* Core (library) mode support: the panic sink, the poisoned-core flag, the
* outbound-result arena, the reset registry, and the sink-routing trap
* funnel. Linked ONLY into core artifacts (every TU of a core archive
* compiles with -DSCR_CORE); executable builds never contain this file —
* their funnel expansion lives in scr_console.c and their session teardown
* stays atexit-driven.
*
* The profile-NAMED symbols (init, sink registration, reset, collect) are
* emitted in the program TU and delegate here, so this file stays
* profile-agnostic and the program TU carries every profile-named symbol —
* one place for the conformance symbol audit to look.
*
* State after a sink call: none is legal. The trap fired mid-operation with
* no unwinding — heap, arena, and collector state are unspecified; the core
* is POISONED. Every runtime-touching entry prologue aborts on the flag
* (deterministic, never heap corruption); recovery is process restart. */
#include "scr_runtime.h"
#ifdef SCR_CORE
#include <stdio.h>
#include <stdlib.h>
/* ── sink registration + poison ───────────────────────────────────────── */
static ScrCoreSinkFn scr_core_sink = NULL;
static void *scr_core_sink_ctx = NULL;
static bool scr_core_poisoned = false;
void scr_core_set_sink(ScrCoreSinkFn fn, void *ctx) {
/* Latest registration wins; re-registration is permitted before a trap.
* Deliberately NOT poison-guarded: a pure store, touching no runtime
* state — but a poisoned core's entries abort regardless of the sink. */
scr_core_sink = fn;
scr_core_sink_ctx = ctx;
}
/* ── the trap funnel, core expansion ──────────────────────────────────────
* Poison first (the sink may longjmp to a host frame below the entry — the
* conforming survival pattern), then deliver exactly once, then abort:
* before registration, or if the sink returns (the ruled host-contract
* violation). The address is the funnel frame's return address — the trap
* site — 0 where the toolchain cannot supply one. */
#if defined(__GNUC__) || defined(__clang__)
#define SCR_TRAP_ADDR() ((uint64_t)(uintptr_t)__builtin_return_address(0))
#else
#define SCR_TRAP_ADDR() ((uint64_t)0)
#endif
static _Noreturn void scr_core_trap_deliver(const char *msg, size_t len, uint64_t addr) {
scr_core_poisoned = true;
if (scr_core_sink != NULL) {
scr_core_sink(scr_core_sink_ctx, (const uint8_t *)msg, len, addr);
}
abort();
}
__attribute__((noinline)) _Noreturn void scr_trap(const char *msg) {
scr_core_trap_deliver(msg, strlen(msg), SCR_TRAP_ADDR());
}
__attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
static char buf[512]; /* no malloc on the invariant-failure path */
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(buf, sizeof buf, fmt, ap);
va_end(ap);
size_t len = n < 0 ? 0 : (size_t)n >= sizeof buf ? sizeof buf - 1 : (size_t)n;
scr_core_trap_deliver(buf, len, SCR_TRAP_ADDR());
}
/* ── entry prologues ──────────────────────────────────────────────────── */
void scr_core_entry(bool reset_arena) {
/* A poisoned core's entries abort deterministically — never through the
* sink again (it received its exactly-once message when the trap fired),
* never into a heap whose invariants already failed. */
if (scr_core_poisoned) abort();
if (reset_arena) scr_core_arena_reset();
}
/* ── the outbound-result arena ────────────────────────────────────────────
* Buffer-class results (string/bytes) MOVE in here; the host reads through
* borrowed pointers until the arena resets (per-entry under the auto
* posture, host-cycled when the profile declares a reset symbol; init and
* collect always reset). */
typedef struct {
void *v;
bool is_str; /* ScrStr vs ScrBytes — picks the release */
} ScrCoreArenaEnt;
static ScrCoreArenaEnt *scr_core_arena = NULL;
static size_t scr_core_arena_n = 0, scr_core_arena_cap = 0;
static void scr_core_arena_keep(void *v, bool is_str) {
if (scr_core_arena_n == scr_core_arena_cap) {
scr_core_arena_cap = scr_core_arena_cap ? scr_core_arena_cap * 2 : 16;
scr_core_arena = realloc(scr_core_arena, scr_core_arena_cap * sizeof *scr_core_arena);
if (!scr_core_arena) scr_trap("scriptc: out of memory\n");
}
scr_core_arena[scr_core_arena_n].v = v;
scr_core_arena[scr_core_arena_n].is_str = is_str;
scr_core_arena_n++;
}
void scr_core_arena_reset(void) {
for (size_t i = 0; i < scr_core_arena_n; i++) {
if (scr_core_arena[i].is_str) scr_str_release((ScrStr *)scr_core_arena[i].v);
else scr_bytes_release((ScrBytes *)scr_core_arena[i].v);
}
scr_core_arena_n = 0;
}
void scr_core_collect(void) {
scr_core_arena_reset();
scr_collect_cycles();
}
/* ── marshalling helpers (both emissions call exactly these) ──────────── */
ScrStr *scr_core_str_in(const uint8_t *p, size_t len) {
return scr_str_new((const char *)p, len);
}
ScrBytes *scr_core_bytes_in(const uint8_t *p, size_t len) {
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
if (b == NULL) {
/* scr_bytes_new throws only for lengths past 2^53-1 — an impossible
* host buffer; funnel the contract violation instead of a NULL deref. */
scr_exc_clear();
scr_trap("scriptc: core inbound bytes length out of range\n");
}
if (len > 0) memcpy(b->data, p, len);
return b;
}
void scr_core_str_out(ScrStr *s, const uint8_t **out, size_t *out_len) {
scr_core_arena_keep(s, true);
*out = (const uint8_t *)s->data; /* NUL-terminated after len (ScrStr layout) */
*out_len = s->len;
}
void scr_core_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len) {
scr_core_arena_keep(b, false);
*out = b->data; /* elem is always u8 at the boundary (v1 bytes class) */
*out_len = b->len;
}
/* ── the reset registry + session reset ─────────────────────────────────
* Where an executable's always-linked units atexit() their lazy teardowns,
* core builds register here (scr_atexit in scr_runtime.h): registered
* once, drained on EVERY reset — re-registration guards in the units stay
* satisfied because the entry persists. */
#define SCR_CORE_MAX_RESETS 32
static void (*scr_core_resets[SCR_CORE_MAX_RESETS])(void);
static size_t scr_core_nresets = 0;
void scr_core_register_reset(void (*fn)(void)) {
for (size_t i = 0; i < scr_core_nresets; i++) {
if (scr_core_resets[i] == fn) return;
}
if (scr_core_nresets == SCR_CORE_MAX_RESETS) {
scr_trap("scriptc: internal error: core reset registry overflow\n");
}
scr_core_resets[scr_core_nresets++] = fn;
}
#ifdef SCR_RC_AUDIT
extern long scr_str_live_count(void); /* scr_string.c */
extern long scr_arr_live_count(void); /* scr_array.c */
extern long scr_map_live_count(void); /* scr_map.c */
extern long scr_box_live_count(void); /* scr_closure.c */
extern long scr_closure_live_count(void); /* scr_closure.c */
extern long scr_obj_live_count(void); /* scr_object.c */
extern long scr_union_live_count(void); /* scr_union.c */
extern long scr_dyn_live_count(void); /* scr_json.c */
extern long scr_bytes_live_count(void); /* scr_bytes.c */
/* The per-session heap-emptiness assertion (the audit flavor's determinism
* seam): after a full reset the live counters must all read zero, or the
* previous session leaked. A failure is a TRAP through the sink — the
* executable audit's _Exit(99) stays exe-lane-only. */
static void scr_core_audit_zero(void) {
long strings = scr_str_live_count(), arrays = scr_arr_live_count(),
maps = scr_map_live_count(), boxes = scr_box_live_count(),
closures = scr_closure_live_count(), objects = scr_obj_live_count(),
unions = scr_union_live_count(), dyns = scr_dyn_live_count(),
bytes = scr_bytes_live_count();
if (strings != 0 || arrays != 0 || maps != 0 || boxes != 0 || closures != 0 ||
objects != 0 || unions != 0 || dyns != 0 || bytes != 0) {
scr_trap_fmt(
"scriptc CORE RC AUDIT FAILED: %ld heap string(s), %ld array(s), "
"%ld map(s), %ld box(es), %ld closure(s), %ld object(s), "
"%ld union(s), %ld dyn value(s), %ld bytes value(s) live across re-init\n",
strings, arrays, maps, boxes, closures, objects, unions, dyns, bytes);
}
}
#endif /* SCR_RC_AUDIT */
extern void scr_lib_core_cleanup(void); /* scr_lib.c: the interned process values */
void scr_core_reset(void) {
/* Called by the generated init entry AFTER the program TU released and
* zeroed its globals (run-once guards included) — the same order the
* executable exit path runs: library cleanup → cycle collection → RC
* audit. Everything here is re-runnable; the first call is a cheap
* no-op pass. */
scr_exc_clear();
scr_core_arena_reset();
for (size_t i = 0; i < scr_core_nresets; i++) scr_core_resets[i]();
scr_lib_core_cleanup();
scr_collect_cycles();
#ifdef SCR_RC_AUDIT
scr_core_audit_zero();
#endif
}
#endif /* SCR_CORE */
+1 -2
View File
@@ -39,8 +39,7 @@
#define SCR_RC(obj) (*(size_t *)(obj))
static void scr_cyc_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
void *scr_cyc_alloc(size_t size, ScrTraceFn trace, ScrCycFreeFn free_fn) {
+2 -4
View File
@@ -14,8 +14,7 @@
#include <stdlib.h>
static void scr_error_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
static bool scr_error_traced = false;
@@ -247,8 +246,7 @@ void scr_undef_global_read(ScrStr *name) {
size_t len = name->len + sizeof suffix - 1;
char *msg = malloc(len + 1);
if (!msg) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(msg, name->data, name->len);
memcpy(msg + name->len, suffix, sizeof suffix);
+1 -2
View File
@@ -54,8 +54,7 @@
#endif
static void scr_ee_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* One registered listener. Shared between the live list and emit
+65 -2
View File
@@ -60,8 +60,7 @@ void scr_exc_clear(void) { scr_exc_reset(); }
ScrCaught *scr_exc_take(void) {
ScrCaught *c = calloc(1, sizeof *c);
if (!c) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
c->rc = 1;
c->kind = scr_exc_kind;
@@ -154,6 +153,70 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) {
return scr_str_new("", 0);
}
#ifdef SCR_CORE
/* An exception that escaped user code into a generated entry wrapper: the
* host contract is that declared entries do not throw, so an escape is a
* contract-shaped failure exactly like a trap. Render the same "Uncaught
* ..." first line the executable epilogue prints (scr_exc_print_uncaught's
* arms, buffer-writing), release the payload, and route the text through
* the trap funnel — one failure channel at the boundary. */
void scr_core_check_exc(void) {
if (!scr_exc_pending()) return;
static char buf[1024]; /* the message is copied out before the payload dies */
size_t n = 0;
const char prefix[] = "Uncaught ";
memcpy(buf, prefix, sizeof prefix - 1);
n = sizeof prefix - 1;
const size_t cap = sizeof buf - 2; /* room for '\n' + NUL */
switch (scr_exc_kind) {
case SCR_EXC_F64:
n += scr_f64_to_str(scr_exc_f64, buf + n);
break;
case SCR_EXC_BOOL: {
const char *b = scr_exc_bool ? "true" : "false";
size_t bl = strlen(b);
memcpy(buf + n, b, bl);
n += bl;
break;
}
case SCR_EXC_STR: {
const ScrStr *s = (const ScrStr *)scr_exc_payload;
size_t take = s->len > cap - n ? cap - n : s->len;
memcpy(buf + n, s->data, take);
n += take;
break;
}
case SCR_EXC_OBJ:
if (scr_error_is(scr_exc_payload)) {
ScrStr *s = scr_error_to_string((ScrError *)scr_exc_payload);
size_t take = s->len > cap - n ? cap - n : s->len;
memcpy(buf + n, s->data, take);
n += take;
scr_str_release(s);
break;
}
/* fall through: non-Error hierarchy objects render like other refs */
case SCR_EXC_REF: {
const char obj[] = "[object]";
memcpy(buf + n, obj, sizeof obj - 1);
n += sizeof obj - 1;
break;
}
case SCR_EXC_NONE:
case SCR_EXC_GENRET: { /* unreachable through the pending() gate */
const char none[] = "(no exception)";
memcpy(buf + n, none, sizeof none - 1);
n += sizeof none - 1;
break;
}
}
buf[n++] = '\n';
buf[n] = '\0';
scr_exc_reset(); /* the payload is released before the funnel poisons */
scr_trap(buf);
}
#endif /* SCR_CORE */
void scr_throw_f64(double v) {
scr_exc_reset();
scr_exc_kind = SCR_EXC_F64;
+1 -2
View File
@@ -31,8 +31,7 @@
#include <string.h>
static void insp_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── a tiny append-only byte buffer ──────────────────────────────────── */
+11 -20
View File
@@ -36,8 +36,7 @@ long scr_dyn_live_count(void) { return scr_live_dyns; }
#endif
static void scr_json_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── output buffer ─────────────────────────────────────────────────────
@@ -421,8 +420,7 @@ void scr_dyn_handle_install(ScrDynHandleTag tag, const ScrDynHandleOps *ops) {
static const ScrDynHandleOps *scr_dyn_handle_ops(ScrDynHandleTag tag) {
const ScrDynHandleOps *ops = scr_dynh_ops[tag];
if (!ops) {
fputs("scriptc: internal error: dyn handle ops not installed\n", stderr);
abort();
scr_trap("scriptc: internal error: dyn handle ops not installed\n");
}
return ops;
}
@@ -501,8 +499,7 @@ static ScrDynThisEnt *scr_dyn_this_grow(void) {
size_t cap = scr_dyn_this_cap ? scr_dyn_this_cap * 2 : 8;
ScrDynThisEnt *s = realloc(scr_dyn_this_stack, cap * sizeof *s);
if (!s) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
scr_dyn_this_stack = s;
scr_dyn_this_cap = cap;
@@ -526,8 +523,7 @@ void scr_dyn_this_push_dyn(const ScrDyn *v) {
void scr_dyn_this_pop(void) {
if (scr_dyn_this_n == 0) {
fputs("scriptc: internal error: receiver stack underflow\n", stderr);
abort();
scr_trap("scriptc: internal error: receiver stack underflow\n");
}
ScrDynThisEnt *e = &scr_dyn_this_stack[--scr_dyn_this_n];
if (e->dv) scr_dyn_release(e->dv);
@@ -763,13 +759,12 @@ ScrDyn *scr_dyn_from_error(const ScrError *e) {
scr_errdyn_cap = scr_errdyn_cap ? scr_errdyn_cap * 2 : 8;
scr_errdyn_cache = realloc(scr_errdyn_cache, scr_errdyn_cap * sizeof *scr_errdyn_cache);
if (!scr_errdyn_cache) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
if (!scr_errdyn_teardown_registered) {
scr_errdyn_teardown_registered = true;
atexit(scr_errdyn_teardown);
scr_atexit(scr_errdyn_teardown);
}
scr_errdyn_cache[scr_errdyn_n].err = scr_error_retain((ScrError *)e);
scr_errdyn_cache[scr_errdyn_n].dyn = scr_dyn_retain(d);
@@ -824,13 +819,12 @@ void scr_errdyn_put(ScrError *e, ScrDyn *d) {
scr_errdyn_cap = scr_errdyn_cap ? scr_errdyn_cap * 2 : 8;
scr_errdyn_cache = realloc(scr_errdyn_cache, scr_errdyn_cap * sizeof *scr_errdyn_cache);
if (!scr_errdyn_cache) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
if (!scr_errdyn_teardown_registered) {
scr_errdyn_teardown_registered = true;
atexit(scr_errdyn_teardown);
scr_atexit(scr_errdyn_teardown);
}
scr_errdyn_cache[scr_errdyn_n].err = scr_error_retain(e);
scr_errdyn_cache[scr_errdyn_n].dyn = scr_dyn_retain(d);
@@ -1035,8 +1029,7 @@ void scr_jb_put_dyn(ScrJsonBuf *b, const ScrDyn *d) {
return;
}
}
fputs("scriptc: internal error: invalid dyn kind\n", stderr);
abort();
scr_trap("scriptc: internal error: invalid dyn kind\n");
}
/* ── dynCheck failure path ─────────────────────────────────────────────── */
@@ -1750,8 +1743,7 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
size_t len = what->len + sizeof suffix - 1;
char *msg = malloc(len + 1);
if (!msg) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(msg, what->data, what->len);
memcpy(msg + what->len, suffix, sizeof suffix);
@@ -1860,8 +1852,7 @@ static ScrDyn *scr_dyn_objwalk(const ScrDyn *v, ScrObjWalk mode) {
bool *is_index = malloc(n ? n * sizeof *is_index : 1);
double *idx = malloc(n ? n * sizeof *idx : 1);
if (!is_index || !idx) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t index_count = 0;
for (size_t i = 0; i < n; i++) {
+44 -66
View File
@@ -132,6 +132,14 @@ void scr_lib_init(int argc, char **argv) {
atexit(scr_lib_cleanup);
}
#ifdef SCR_CORE
/* Core builds never call scr_lib_init (a core has no argv and registers no
* atexit handlers); the interned process values above still intern lazily
* on first read, so the core reset seam releases them here instead —
* scr_core_reset (scr_core.c) calls this every session reset. */
void scr_lib_core_cleanup(void) { scr_lib_cleanup(); }
#endif
/* Raw argv accessors for the island's process shim (scr_island.c): the
* island's process.argv must match the static world's ["scriptc",
* argv[0], ...] shape exactly, so both build from the same stash. */
@@ -261,8 +269,7 @@ ScrStr *scr_env_get(const ScrStr *name) {
if (need == 0) return NULL; /* absent (an empty value still needs its NUL) */
char *buf = malloc(need);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
DWORD got = GetEnvironmentVariableA(name->data, buf, need);
ScrStr *s = scr_str_new(buf, got);
@@ -553,8 +560,7 @@ bool scr_process_kill_named(double pid, const ScrStr *signal) {
size_t cap = 16 + signal->len + 1;
char *msg = malloc(cap);
if (!msg) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
int len = snprintf(msg, cap, "Unknown signal: %s", signal->data);
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
@@ -567,8 +573,7 @@ bool scr_process_kill_named(double pid, const ScrStr *signal) {
ScrStr *scr_process_cwd(void) {
char buf[4096];
if (!getcwd(buf, sizeof buf)) {
fputs("scriptc: process.cwd() failed\n", stderr);
abort();
scr_trap("scriptc: process.cwd() failed\n");
}
return scr_str_new(buf, strlen(buf));
}
@@ -612,16 +617,14 @@ bool scr_proc_stream_write(double fd, const ScrStr *data) {
ScrStr *scr_os_homedir(void) {
const char *home = getenv("USERPROFILE");
if (home && home[0] != '\0') return scr_str_new(home, strlen(home));
fputs("scriptc: os.homedir() failed\n", stderr);
abort();
scr_trap("scriptc: os.homedir() failed\n");
}
ScrStr *scr_os_user_name(void) {
char buf[UNLEN + 1];
DWORD n = sizeof buf;
if (!GetUserNameA(buf, &n) || n == 0) {
fputs("scriptc: os.userInfo() failed\n", stderr);
abort();
scr_trap("scriptc: os.userInfo() failed\n");
}
return scr_str_new(buf, n - 1); /* n counts the NUL */
}
@@ -648,8 +651,7 @@ ScrStr *scr_os_release(void) {
RtlGetVersionFn fn =
ntdll ? (RtlGetVersionFn)(void *)GetProcAddress(ntdll, "RtlGetVersion") : NULL;
if (fn == NULL || fn(&info) != 0) {
fputs("scriptc: os.release() failed\n", stderr);
abort();
scr_trap("scriptc: os.release() failed\n");
}
char buf[64];
int len = snprintf(buf, sizeof buf, "%lu.%lu.%lu", (unsigned long)info.dwMajorVersion,
@@ -676,8 +678,7 @@ ScrStr *scr_os_tmpdir(void) {
char buf[MAX_PATH + 2];
DWORD n = GetTempPathA(sizeof buf, buf);
if (n == 0 || n >= sizeof buf) {
fputs("scriptc: os.tmpdir() failed\n", stderr);
abort();
scr_trap("scriptc: os.tmpdir() failed\n");
}
size_t len = n;
if (len > 1 && (buf[len - 1] == '\\' || buf[len - 1] == '/')) len--;
@@ -693,8 +694,7 @@ ScrStr *scr_os_homedir(void) {
struct passwd *result = NULL;
char buf[8192];
if (getpwuid_r(getuid(), &pw, buf, sizeof buf, &result) != 0 || !result || !result->pw_dir) {
fputs("scriptc: os.homedir() failed\n", stderr);
abort();
scr_trap("scriptc: os.homedir() failed\n");
}
return scr_str_new(result->pw_dir, strlen(result->pw_dir));
}
@@ -706,8 +706,7 @@ ScrStr *scr_os_homedir(void) {
static const struct passwd *scr_os_passwd(char *buf, size_t cap, struct passwd *pw) {
struct passwd *result = NULL;
if (getpwuid_r(getuid(), pw, buf, cap, &result) != 0 || !result) {
fputs("scriptc: os.userInfo() failed\n", stderr);
abort();
scr_trap("scriptc: os.userInfo() failed\n");
}
return result;
}
@@ -740,8 +739,7 @@ ScrStr *scr_os_release(void) {
/* uname(2)'s release field — Node's uv_os_uname()-backed answer. */
struct utsname u;
if (uname(&u) != 0) {
fputs("scriptc: os.release() failed\n", stderr);
abort();
scr_trap("scriptc: os.release() failed\n");
}
return scr_str_new(u.release, strlen(u.release));
}
@@ -750,8 +748,7 @@ ScrStr *scr_os_type(void) {
/* uname(2)'s sysname field ("Darwin", "Linux") — Node's os.type(). */
struct utsname u;
if (uname(&u) != 0) {
fputs("scriptc: os.type() failed\n", stderr);
abort();
scr_trap("scriptc: os.type() failed\n");
}
return scr_str_new(u.sysname, strlen(u.sysname));
}
@@ -824,20 +821,20 @@ struct ScrIfaddrs {
* stance. */
ScrIfaddrs *scr_os_ifaddrs(void) {
ScrIfaddrs *s = calloc(1, sizeof *s);
if (!s) abort();
if (!s) scr_trap("scriptc: out of memory\n");
ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER;
ULONG size = 16 * 1024;
IP_ADAPTER_ADDRESSES *adapters = NULL;
for (int tries = 0; tries < 4; tries++) {
adapters = realloc(adapters, size);
if (!adapters) abort();
if (!adapters) scr_trap("scriptc: out of memory\n");
ULONG rc = GetAdaptersAddresses(AF_UNSPEC, flags, NULL, adapters, &size);
if (rc == ERROR_SUCCESS) break;
if (rc != ERROR_BUFFER_OVERFLOW) {
free(adapters);
s->rows = calloc(1, sizeof *s->rows);
if (!s->rows) abort();
if (!s->rows) scr_trap("scriptc: out of memory\n");
return s; /* empty snapshot */
}
}
@@ -850,7 +847,7 @@ ScrIfaddrs *scr_os_ifaddrs(void) {
}
}
s->rows = calloc(count ? count : 1, sizeof *s->rows);
if (!s->rows) abort();
if (!s->rows) scr_trap("scriptc: out of memory\n");
for (IP_ADAPTER_ADDRESSES *a = adapters; a != NULL; a = a->Next) {
if (a->OperStatus != IfOperStatusUp || a->FirstUnicastAddress == NULL) continue;
char name[64] = "";
@@ -934,7 +931,7 @@ static int scr_netmask_prefix(const unsigned char *bytes, size_t len) {
ScrIfaddrs *scr_os_ifaddrs(void) {
ScrIfaddrs *s = calloc(1, sizeof *s);
if (!s) abort();
if (!s) scr_trap("scriptc: out of memory\n");
struct ifaddrs *addrs = NULL;
if (getifaddrs(&addrs) != 0) return s;
size_t count = 0;
@@ -942,7 +939,7 @@ ScrIfaddrs *scr_os_ifaddrs(void) {
if (scr_ifaddr_row_ok(ent)) count++;
}
s->rows = calloc(count ? count : 1, sizeof *s->rows);
if (!s->rows) abort();
if (!s->rows) scr_trap("scriptc: out of memory\n");
for (struct ifaddrs *ent = addrs; ent != NULL; ent = ent->ifa_next) {
if (!scr_ifaddr_row_ok(ent)) continue;
ScrIfaddrRow *row = &s->rows[s->n++];
@@ -1409,8 +1406,7 @@ void scr_fs_throw(int e, const char *op, const ScrStr *path) {
size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown) + 8;
char *msg = malloc(cap);
if (!msg) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
int len = snprintf(msg, cap, "%s: %s, %s '%s'", name, text, op, shown);
/* A real Error instance (name "Error", message = Node's text) — what a
@@ -1433,16 +1429,14 @@ ScrStr *scr_fs_read_file(ScrStr *path) {
size_t cap = 4096, len = 0;
char *buf = malloc(cap);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
for (;;) {
if (cap - len < 2048) {
cap *= 2;
char *grown = realloc(buf, cap);
if (!grown) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
buf = grown;
}
@@ -1688,8 +1682,7 @@ static void scr_fs_throw2(int e, const char *op, const ScrStr *src, const ScrStr
size_t cap = strlen(name) + strlen(text) + strlen(op) + strlen(shown_src) + strlen(shown_dest) + 16;
char *msg = malloc(cap);
if (!msg) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
int len = snprintf(msg, cap, "%s: %s, %s '%s' -> '%s'", name, text, op, shown_src, shown_dest);
scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)len, name);
@@ -1846,8 +1839,7 @@ static void scr_mkdir_rec(char *path, size_t len, mode_t mode) {
void scr_fs_mkdir_recursive(ScrStr *path) {
char *buf = malloc(path->len + 1);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(buf, path->data, path->len + 1); /* ScrStr data is NUL-terminated */
scr_mkdir_rec(buf, path->len, 0777);
@@ -1859,8 +1851,7 @@ void scr_fs_mkdir_recursive(ScrStr *path) {
void scr_fs_mkdir_recursive_mode(ScrStr *path, double mode) {
char *buf = malloc(path->len + 1);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(buf, path->data, path->len + 1);
scr_mkdir_rec(buf, path->len, (mode_t)mode);
@@ -1907,8 +1898,7 @@ static void scr_rm_tree_e(const char *path, size_t len, ScrRmFail *f) {
size_t namelen = strlen(ent->d_name);
char *child = malloc(len + 1 + namelen + 1);
if (!child) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(child, path, len);
child[len] = '/';
@@ -2011,8 +2001,7 @@ static char *scr_win_mkdtemp(char *tmpl) {
ScrStr *scr_fs_mkdtemp(ScrStr *prefix) {
char *tmpl = malloc(prefix->len + 7);
if (!tmpl) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
memcpy(tmpl, prefix->data, prefix->len);
memcpy(tmpl + prefix->len, "XXXXXX", 7);
@@ -2059,16 +2048,14 @@ static char *scr_read_fd_all(double fd, size_t *out_len) {
size_t cap = 4096, len = 0;
char *buf = malloc(cap);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
for (;;) {
if (cap - len < 2048) {
cap *= 2;
char *grown = realloc(buf, cap);
if (!grown) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
buf = grown;
}
@@ -2276,8 +2263,7 @@ double scr_stats_mtime_ms(ScrStats *s) { return s->mtime_ms; }
static ScrStats *scr_stats_of(const struct stat *st) {
ScrStats *s = malloc(sizeof(ScrStats));
if (!s) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
s->rc = 1;
s->is_file = S_ISREG(st->st_mode);
@@ -2374,16 +2360,14 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
}
ScrScandir *s = malloc(sizeof *s);
if (!s) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
s->len = 0;
s->cap = 8;
s->names = malloc(s->cap * sizeof *s->names);
s->kinds = malloc(s->cap);
if (!s->names || !s->kinds) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
const struct dirent *ent;
while ((ent = readdir(d)) != NULL) {
@@ -2424,8 +2408,7 @@ ScrScandir *scr_fs_scandir(ScrStr *path) {
s->names = realloc(s->names, s->cap * sizeof *s->names);
s->kinds = realloc(s->kinds, s->cap);
if (!s->names || !s->kinds) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
s->names[s->len] = scr_str_new(ent->d_name, strlen(ent->d_name));
@@ -2535,8 +2518,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
size_t size = (size_t)n;
unsigned char *bytes = malloc(size ? size : 1);
if (!bytes) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
arc4random_buf(bytes, size);
ScrStr *out;
@@ -2544,8 +2526,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
static const char hex[] = "0123456789abcdef";
char *buf = malloc(size * 2 + 1);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
for (size_t i = 0; i < size; i++) {
buf[i * 2] = hex[bytes[i] >> 4];
@@ -2559,8 +2540,7 @@ ScrStr *scr_crypto_random_string(double n, ScrStr *enc) {
size_t outlen = (size + 2) / 3 * 4;
char *buf = malloc(outlen + 1);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t o = 0;
for (size_t i = 0; i < size; i += 3) {
@@ -2899,8 +2879,7 @@ static bool scr_x509_der(const uint8_t *in, size_t n, const uint8_t **der, size_
}
uint8_t *out = malloc(((size_t)(stop - b) / 4 + 1) * 3 + 3);
if (!out) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t o = 0;
unsigned acc = 0;
@@ -3116,8 +3095,7 @@ static uint32_t scr_to_uint32(double d);
static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) {
char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */
if (!out) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t o = 0;
for (size_t i = 0; i < n; i++) {
+2 -4
View File
@@ -31,8 +31,7 @@ long scr_map_live_count(void) { return scr_live_maps; }
#define SCR_MAP_EMPTY SIZE_MAX
static void scr_map_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── key normalization + hashing (SameValueZero) ───────────────────────
@@ -462,8 +461,7 @@ bool scr_map_iter_live(const ScrMap *m, double i) {
static const ScrMapEntry *scr_map_iter_at(const ScrMap *m, double i) {
if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].live) {
fputs("scriptc: internal error: map iteration index out of range\n", stderr);
abort();
scr_trap("scriptc: internal error: map iteration index out of range\n");
}
return &m->entries[(size_t)i];
}
+4 -8
View File
@@ -37,8 +37,7 @@ static void pb_init(PathBuf *b) {
b->len = 0;
b->data = malloc(b->cap);
if (!b->data) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
@@ -47,8 +46,7 @@ static void pb_reserve(PathBuf *b, size_t extra) {
while (b->len + extra > b->cap) b->cap *= 2;
char *grown = realloc(b->data, b->cap);
if (!grown) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
b->data = grown;
}
@@ -226,8 +224,7 @@ ScrStr *scr_path_join(ScrArr *parts) {
static void scr_path_cwd(PathBuf *out) {
char buf[4096];
if (!getcwd(buf, sizeof buf)) {
fputs("scriptc: path.resolve: getcwd failed\n", stderr);
abort();
scr_trap("scriptc: path.resolve: getcwd failed\n");
}
pb_append(out, buf, strlen(buf));
}
@@ -984,8 +981,7 @@ ScrStr *scr_path_win32_relative(ScrStr *from, ScrStr *to) {
char *flow = malloc(rfrom->len ? rfrom->len : 1);
char *tlow = malloc(rto->len ? rto->len : 1);
if (!flow || !tlow) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
for (size_t k = 0; k < rfrom->len; k++) flow[k] = scr_path_w32_lower(rfrom->data[k]);
for (size_t k = 0; k < rto->len; k++) tlow[k] = scr_path_w32_lower(rto->data[k]);
+16 -25
View File
@@ -37,8 +37,7 @@
#include "libregexp.h"
static void scr_regex_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── libregexp host hooks ─────────────────────────────────────────────
@@ -102,7 +101,7 @@ static void scr_note_compiled(ScrRegex *re) {
scr_compiled = realloc(scr_compiled, scr_compiled_cap * sizeof *scr_compiled);
if (!scr_compiled) scr_regex_oom();
}
if (scr_compiled_len == 0) atexit(scr_regex_free_bytecodes);
if (scr_compiled_len == 0) scr_atexit(scr_regex_free_bytecodes);
scr_compiled[scr_compiled_len++] = re;
}
@@ -119,9 +118,8 @@ static int scr_lre_flags(const ScrStr *flags) {
case 'u': mask |= LRE_FLAG_UNICODE; break;
case 'y': mask |= LRE_FLAG_STICKY; break;
default:
fprintf(stderr, "scriptc: internal error: unexpected regex flag '%c'\n",
flags->data[i]);
abort();
scr_trap_fmt("scriptc: internal error: unexpected regex flag '%c'\n",
flags->data[i]);
}
}
return mask;
@@ -197,9 +195,8 @@ static uint8_t *scr_regex_bc(ScrRegex *re) {
* tsc's parser has already caught plain syntax errors, so this is
* rare). */
fflush(stdout);
fprintf(stderr, "scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
re->source->data, re->flags->data, error_msg);
abort();
scr_trap_fmt("scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
re->source->data, re->flags->data, error_msg);
}
re->bc = bc;
scr_note_compiled(re);
@@ -315,8 +312,7 @@ static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
int rc = lre_exec(capture, bc, (const uint8_t *)u, index, len, 1, lre_opaque());
if (rc < 0) {
fflush(stdout);
fputs("scriptc: regular expression execution failed\n", stderr);
abort();
scr_trap("scriptc: regular expression execution failed\n");
}
return rc;
}
@@ -351,11 +347,9 @@ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
* iteration this slice does not model (the frontend rejects the sites
* it can see; values that flow through variables land here). */
fflush(stdout);
fprintf(stderr,
"scriptc: test() on a regex with the 'g' or 'y' flag is not "
"supported (stateful lastIndex); drop the flag, or use "
"replace/replaceAll/split\n");
abort();
scr_trap("scriptc: test() on a regex with the 'g' or 'y' flag is not "
"supported (stateful lastIndex); drop the flag, or use "
"replace/replaceAll/split\n");
}
int len;
uint16_t *u = scr_to_utf16(s, &len);
@@ -379,11 +373,9 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
uint8_t *bc = scr_regex_bc(re);
if (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
fflush(stdout);
fprintf(stderr,
"scriptc: match() on a regex with the 'g' or 'y' flag is not "
"supported (an every-match array is a different shape); drop the "
"flag, or use replace/replaceAll/split\n");
abort();
scr_trap("scriptc: match() on a regex with the 'g' or 'y' flag is not "
"supported (an every-match array is a different shape); drop the "
"flag, or use replace/replaceAll/split\n");
}
int len;
uint16_t *u = scr_to_utf16(s, &len);
@@ -803,7 +795,7 @@ static bool scr_assert_regex_hits(ScrRegex *re, ScrStr *s) {
static ScrStr *scr_assert_regex_render(ScrRegex *re) {
size_t cap = re->source->len + re->flags->len + 2;
char *buf = malloc(cap);
if (!buf) abort();
if (!buf) scr_trap("scriptc: out of memory\n");
size_t n = 0;
buf[n++] = '/';
memcpy(buf + n, re->source->data, re->source->len);
@@ -831,7 +823,7 @@ static void scr_assert_regex_input_fail(bool negated, ScrRegex *re, ScrStr *inpu
const char *mid = ". Input:\n\n";
size_t cap = strlen(head) + rre->len + strlen(mid) + insp->len + 1;
char *buf = malloc(cap);
if (!buf) abort();
if (!buf) scr_trap("scriptc: out of memory\n");
size_t n = 0;
memcpy(buf + n, head, strlen(head));
n += strlen(head);
@@ -924,8 +916,7 @@ ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags) {
}
ScrRegex *re = calloc(1, sizeof *re);
if (!re) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
re->rc = 1;
re->source = pattern->len > 0 ? scr_str_retain(pattern) : scr_str_new("(?:)", 4);
+75
View File
@@ -39,6 +39,81 @@ char *strcasestr(const char *hay, const char *needle);
* flush-at-exit, RC audit registration (when built with -DSCR_RC_AUDIT). */
void scr_init(void);
/* ── the trap funnel (scr_console.c; scr_core.c under -DSCR_CORE) ──────
* Every unrecoverable runtime trap — OOM, semantic range traps, internal-
* invariant failures — funnels through this pair instead of open-coded
* fputs/fprintf + abort. Executable builds expand to exactly the historical
* behavior (the message's bytes on stderr, then abort — the default lane
* must not change by a byte). Core builds (-DSCR_CORE) route the message to
* the host-registered panic sink and abort only as the last resort: before
* registration, or if the sink returns (the ruled host-contract violation —
* a conforming sink longjmps to a host frame BELOW the entry, never back
* into core frames). Messages keep their trailing newline in both lanes so
* the funnel is a pure indirection over identical bytes. */
_Noreturn void scr_trap(const char *msg);
_Noreturn void scr_trap_fmt(const char *fmt, ...);
/* ── core (library) mode (scr_core.c, linked only into core artifacts) ──
* A core artifact has no main, no event loop, no signal handlers, no
* atexit registrations, and never touches host stdio modes or buffering:
* initialization runs inside the profile-named init entry (re-runnable
* deterministically), traps route to the sink above, and buffer-class
* results live in a core-owned result arena. Everything here compiles only
* under -DSCR_CORE; executable builds never contain it. */
#ifdef SCR_CORE
typedef struct ScrStr ScrStr; /* full definitions below (C11 repeat) */
typedef struct ScrBytes ScrBytes;
/* The host's panic sink: msg is UTF-8, valid only for the duration of the
* call; address is the trap site's return address (0 when the toolchain
* cannot supply one); ctx is the registration's opaque pointer. The sink
* must not call back into any core entry and must not unwind or longjmp
* back into core frames. */
typedef void (*ScrCoreSinkFn)(void *ctx, const uint8_t *msg, size_t msg_len,
uint64_t address);
void scr_core_set_sink(ScrCoreSinkFn fn, void *ctx); /* latest wins */
/* Entry prologue: aborts deterministically when the core is poisoned (a
* trap already fired — no profile entry may run again; recovery is process
* restart). reset_arena additionally drops the result arena (the
* auto-reset posture, and the reset/collect entries' shared body). */
void scr_core_entry(bool reset_arena);
void scr_core_arena_reset(void);
/* The mode-provided collect entry's body: arena reset + a full cycle
* collection (snapshot-invariant by construction — collection frees only
* unreachable cycles). */
void scr_core_collect(void);
/* Full session reset, called by the generated init entry AFTER the program
* TU released and zeroed its globals: pending-exception clear, arena
* reset, the reset registry's drains (the units' repointed atexit halves),
* the library's interned process values, a cycle collection, and — under
* SCR_RC_AUDIT — the zero-live-heap assertion (a failure is a trap through
* the sink, never _Exit). */
void scr_core_reset(void);
/* Where a unit would atexit() a lazy teardown, core builds register it
* here instead (called on every scr_core_reset, registered once). */
void scr_core_register_reset(void (*fn)(void));
/* An escaped exception at an entry boundary: renders the same "Uncaught
* ..." text the executable epilogue prints, releases the payload, and
* routes the text through the trap funnel. No-op when nothing is pending.
* Defined in scr_exception.c (it owns the cell). */
void scr_core_check_exc(void);
/* Marshalling helpers the generated wrappers call (both emissions share
* these bodies, which is how the two lanes stay identical by
* construction). Inbound is borrowed-and-copied; outbound values MOVE into
* the result arena and stay valid until the next arena reset. String
* results are NUL-terminated after *out_len bytes (ScrStr's layout). */
ScrStr *scr_core_str_in(const uint8_t *p, size_t len); /* +1 */
ScrBytes *scr_core_bytes_in(const uint8_t *p, size_t len); /* +1, u8 */
void scr_core_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
void scr_core_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
#define scr_atexit(fn) scr_core_register_reset(fn)
#else
#define scr_atexit(fn) atexit(fn)
#endif /* SCR_CORE */
/* ── cycle collection (scr_cycle.c) ───────────────────────────────────
* Reference counting alone cannot free cycles, so every object that can
* participate in one — capture boxes, heap closures, unions, promises, and
+2 -4
View File
@@ -15,8 +15,7 @@ long scr_str_live_count(void) { return scr_live_strings; }
#endif
static void scr_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
/* ── UTF-16 index cache ───────────────────────────────────────────────
@@ -519,8 +518,7 @@ ScrStr *scr_str_slice(ScrStr *s, double start, double end) {
ScrStr *scr_str_repeat(ScrStr *s, double count) {
double n = scr_to_integer_or_infinity(count);
if (n < 0 || (isinf(n) && n > 0)) {
fputs("scriptc: RangeError: Invalid count value\n", stderr);
abort();
scr_trap("scriptc: RangeError: Invalid count value\n");
}
if (n == 0 || s->len == 0) return scr_str_empty();
/* n is a finite non-negative integer here. Reject sizes malloc could not
+2 -3
View File
@@ -44,8 +44,7 @@ void scr_sym_release_v(void *p) { scr_sym_release(p); }
ScrSym *scr_sym_new(ScrStr *desc) {
ScrSym *s = malloc(sizeof(ScrSym));
if (!s) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
s->rc = 1;
s->desc = desc ? scr_str_retain(desc) : NULL;
@@ -75,7 +74,7 @@ ScrSym *scr_sym_for(ScrStr *key) {
/* First request for this key: a fresh symbol whose description IS the
* key (the spec's Symbol.for behavior), chained into the registry with
* the registry's own reference. */
if (!g_sym_registry) atexit(scr_sym_registry_cleanup);
if (!g_sym_registry) scr_atexit(scr_sym_registry_cleanup);
ScrSym *s = scr_sym_new(key);
s->reg_key = scr_str_retain(key);
s->reg_next = g_sym_registry;
+9 -16
View File
@@ -69,8 +69,7 @@ static void ub_init(UrlBuf *b) {
b->len = 0;
b->data = malloc(b->cap);
if (!b->data) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
@@ -79,8 +78,7 @@ static void ub_append(UrlBuf *b, const char *bytes, size_t n) {
while (b->len + n > b->cap) b->cap *= 2;
char *grown = realloc(b->data, b->cap);
if (!grown) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
b->data = grown;
}
@@ -182,8 +180,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
size_t seg_starts_cap = 8, seg_count = 0;
size_t *seg_starts = malloc(seg_starts_cap * sizeof(size_t));
if (!seg_starts) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t i = 0;
/* One leading separator opens the first segment (rooted). */
@@ -206,7 +203,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
if (seg_count == seg_starts_cap) {
seg_starts_cap *= 2;
seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
if (!seg_starts) abort();
if (!seg_starts) scr_trap("scriptc: out of memory\n");
}
seg_starts[seg_count++] = out.len;
}
@@ -215,7 +212,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
if (seg_count == seg_starts_cap) {
seg_starts_cap *= 2;
seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
if (!seg_starts) abort();
if (!seg_starts) scr_trap("scriptc: out of memory\n");
}
seg_starts[seg_count++] = out.len;
}
@@ -224,8 +221,7 @@ static ScrStr *parse_rooted_path(const char *raw, size_t len, bool special) {
seg_starts_cap *= 2;
seg_starts = realloc(seg_starts, seg_starts_cap * sizeof(size_t));
if (!seg_starts) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
seg_starts[seg_count++] = out.len;
@@ -355,8 +351,7 @@ ScrUrl *scr_url_new(ScrStr *input) {
while (e > b && (unsigned char)raw[e - 1] <= 0x20) e--;
char *buf = malloc(e - b + 1);
if (!buf) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t len = 0;
for (size_t i = b; i < e; i++) {
@@ -526,8 +521,7 @@ ScrUrl *scr_url_new(ScrStr *input) {
ScrUrl *u = malloc(sizeof(ScrUrl));
if (!u) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
u->rc = 1;
u->scheme = scheme_str;
@@ -738,8 +732,7 @@ ScrStr *scr_url_str_to_path(ScrStr *input) {
static ScrUrl *scr_url_new_file(ScrStr *host, ScrStr *encoded_path) {
ScrUrl *u = malloc(sizeof(ScrUrl));
if (!u) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
u->rc = 1;
u->scheme = scr_str_new("file", 4);
+6 -12
View File
@@ -26,8 +26,7 @@ static void ub_init(UrlBuf *b) {
b->len = 0;
b->data = malloc(b->cap);
if (!b->data) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
}
@@ -36,8 +35,7 @@ static void ub_append(UrlBuf *b, const char *bytes, size_t n) {
while (b->len + n > b->cap) b->cap *= 2;
char *grown = realloc(b->data, b->cap);
if (!grown) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
b->data = grown;
}
@@ -94,8 +92,7 @@ struct ScrSearchParams {
static ScrSearchParams *sp_alloc(void) {
ScrSearchParams *sp = malloc(sizeof(ScrSearchParams));
if (!sp) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
sp->rc = 1;
sp->len = 0;
@@ -103,8 +100,7 @@ static ScrSearchParams *sp_alloc(void) {
sp->names = malloc(sp->cap * sizeof(ScrStr *));
sp->vals = malloc(sp->cap * sizeof(ScrStr *));
if (!sp->names || !sp->vals) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
sp->owner = NULL;
return sp;
@@ -142,8 +138,7 @@ static void sp_push(ScrSearchParams *sp, ScrStr *name, ScrStr *value) {
ScrStr **gn = realloc(sp->names, sp->cap * sizeof(ScrStr *));
ScrStr **gv = realloc(sp->vals, sp->cap * sizeof(ScrStr *));
if (!gn || !gv) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
sp->names = gn;
sp->vals = gv;
@@ -161,8 +156,7 @@ static ScrStr *sp_scrub_utf8(const char *in_c, size_t n) {
const unsigned char *in = (const unsigned char *)in_c;
char *out = malloc(n * 3 + 1);
if (!out) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
size_t o = 0;
uint32_t cp = 0;
+1 -2
View File
@@ -18,8 +18,7 @@
#include <zlib.h>
static void scr_zlib_oom(void) {
fputs("scriptc: out of memory\n", stderr);
abort();
scr_trap("scriptc: out of memory\n");
}
ScrBytes *scr_zlib_deflate(const ScrBytes *data) {