diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index cd9c45005..c0d99be52 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -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); diff --git a/packages/runtime/src/scr_assert.c b/packages/runtime/src/scr_assert.c index 5e68ba515..dd281db41 100644 --- a/packages/runtime/src/scr_assert.c +++ b/packages/runtime/src/scr_assert.c @@ -32,8 +32,7 @@ #include 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 ─────────────── */ diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 88c4996c2..a24e24135 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -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; } diff --git a/packages/runtime/src/scr_bytes_io.c b/packages/runtime/src/scr_bytes_io.c index 390312de0..2376c09b8 100644 --- a/packages/runtime/src/scr_bytes_io.c +++ b/packages/runtime/src/scr_bytes_io.c @@ -12,8 +12,7 @@ #include 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) ────────────────────── */ diff --git a/packages/runtime/src/scr_closure.c b/packages/runtime/src/scr_closure.c index 6884b15af..6f8212fca 100644 --- a/packages/runtime/src/scr_closure.c +++ b/packages/runtime/src/scr_closure.c @@ -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"); } } diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c index 71cf0b41b..2cfee65fe 100644 --- a/packages/runtime/src/scr_console.c +++ b/packages/runtime/src/scr_console.c @@ -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 */ diff --git a/packages/runtime/src/scr_core.c b/packages/runtime/src/scr_core.c new file mode 100644 index 000000000..0ef49b1f5 --- /dev/null +++ b/packages/runtime/src/scr_core.c @@ -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 +#include + +/* ── 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 */ diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index 3bbffd894..9ae4788ca 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -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) { diff --git a/packages/runtime/src/scr_error.c b/packages/runtime/src/scr_error.c index c1855aa7b..90e67ca1d 100644 --- a/packages/runtime/src/scr_error.c +++ b/packages/runtime/src/scr_error.c @@ -14,8 +14,7 @@ #include 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); diff --git a/packages/runtime/src/scr_events_emitter.c b/packages/runtime/src/scr_events_emitter.c index d6e7afc0b..c73eb81a1 100644 --- a/packages/runtime/src/scr_events_emitter.c +++ b/packages/runtime/src/scr_events_emitter.c @@ -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 diff --git a/packages/runtime/src/scr_exception.c b/packages/runtime/src/scr_exception.c index f191b7758..b91f9992b 100644 --- a/packages/runtime/src/scr_exception.c +++ b/packages/runtime/src/scr_exception.c @@ -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; diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index e55a8412c..9b4decbab 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -31,8 +31,7 @@ #include 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 ──────────────────────────────────── */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index aae1f7e18..736f21347 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -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++) { diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 626de9a5b..7b2d4164a 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -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++) { diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index f077ed9ed..0922e11fe 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -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]; } diff --git a/packages/runtime/src/scr_path.c b/packages/runtime/src/scr_path.c index 4050e007f..0a21f3eb3 100644 --- a/packages/runtime/src/scr_path.c +++ b/packages/runtime/src/scr_path.c @@ -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]); diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index f66287869..b2c719e50 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -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); diff --git a/packages/runtime/src/scr_runtime.h b/packages/runtime/src/scr_runtime.h index 6df8b8afd..f36d85dcf 100644 --- a/packages/runtime/src/scr_runtime.h +++ b/packages/runtime/src/scr_runtime.h @@ -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 diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 982ceee9c..11a399062 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -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 diff --git a/packages/runtime/src/scr_symbol.c b/packages/runtime/src/scr_symbol.c index f976cf255..cd2a7a7cf 100644 --- a/packages/runtime/src/scr_symbol.c +++ b/packages/runtime/src/scr_symbol.c @@ -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; diff --git a/packages/runtime/src/scr_url.c b/packages/runtime/src/scr_url.c index 705108ec7..777440518 100644 --- a/packages/runtime/src/scr_url.c +++ b/packages/runtime/src/scr_url.c @@ -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); diff --git a/packages/runtime/src/scr_url_params.c b/packages/runtime/src/scr_url_params.c index ee3e42a21..fd3f5da97 100644 --- a/packages/runtime/src/scr_url_params.c +++ b/packages/runtime/src/scr_url_params.c @@ -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; diff --git a/packages/runtime/src/scr_zlib.c b/packages/runtime/src/scr_zlib.c index a98f7127b..19fafbbe3 100644 --- a/packages/runtime/src/scr_zlib.c +++ b/packages/runtime/src/scr_zlib.c @@ -18,8 +18,7 @@ #include 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) {