@scriptc/runtime 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/scr_array.c +6 -12
- package/src/scr_assert.c +32 -2
- package/src/scr_bytes.c +3 -6
- package/src/scr_bytes_io.c +1 -2
- package/src/scr_closure.c +1 -2
- package/src/scr_console.c +5 -0
- package/src/scr_cycle.c +1 -2
- package/src/scr_error.c +2 -4
- package/src/scr_events_emitter.c +1 -2
- package/src/scr_exception.c +87 -2
- package/src/scr_inspect.c +83 -2
- package/src/scr_json.c +157 -20
- package/src/scr_lib.c +50 -66
- package/src/scr_library.c +222 -0
- package/src/scr_map.c +2 -4
- package/src/scr_path.c +4 -8
- package/src/scr_regex.c +16 -25
- package/src/scr_runtime.h +127 -1
- package/src/scr_string.c +2 -4
- package/src/scr_symbol.c +2 -3
- package/src/scr_url.c +9 -16
- package/src/scr_url_params.c +6 -12
- package/src/scr_zlib.c +1 -2
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* Library mode support: the panic sink, the poisoned-library flag, the
|
|
2
|
+
* outbound-result arena, the reset registry, and the sink-routing trap
|
|
3
|
+
* funnel. Linked ONLY into library artifacts (every TU of a library archive
|
|
4
|
+
* compiles with -DSCR_LIB); executable builds never contain this file —
|
|
5
|
+
* their funnel expansion lives in scr_console.c and their session teardown
|
|
6
|
+
* stays atexit-driven.
|
|
7
|
+
*
|
|
8
|
+
* The profile-NAMED symbols (init, sink registration, reset, collect) are
|
|
9
|
+
* emitted in the program TU and delegate here, so this file stays
|
|
10
|
+
* profile-agnostic and the program TU carries every profile-named symbol —
|
|
11
|
+
* one place for the conformance symbol audit to look.
|
|
12
|
+
*
|
|
13
|
+
* State after a sink call: none is legal. The trap fired mid-operation with
|
|
14
|
+
* no unwinding — heap, arena, and collector state are unspecified; the library
|
|
15
|
+
* is POISONED. Every runtime-touching entry prologue aborts on the flag
|
|
16
|
+
* (deterministic, never heap corruption); recovery is process restart. */
|
|
17
|
+
#include "scr_runtime.h"
|
|
18
|
+
|
|
19
|
+
#ifdef SCR_LIB
|
|
20
|
+
|
|
21
|
+
#include <stdio.h>
|
|
22
|
+
#include <stdlib.h>
|
|
23
|
+
|
|
24
|
+
/* ── sink registration + poison ───────────────────────────────────────── */
|
|
25
|
+
|
|
26
|
+
static ScrLibSinkFn scr_library_sink = NULL;
|
|
27
|
+
static void *scr_library_sink_ctx = NULL;
|
|
28
|
+
static bool scr_library_poisoned = false;
|
|
29
|
+
|
|
30
|
+
void scr_library_set_sink(ScrLibSinkFn fn, void *ctx) {
|
|
31
|
+
/* Latest registration wins; re-registration is permitted before a trap.
|
|
32
|
+
* Deliberately NOT poison-guarded: a pure store, touching no runtime
|
|
33
|
+
* state — but a poisoned library's entries abort regardless of the sink. */
|
|
34
|
+
scr_library_sink = fn;
|
|
35
|
+
scr_library_sink_ctx = ctx;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* ── the trap funnel, library expansion ───────────────────────────────────
|
|
39
|
+
* Poison first (the sink may longjmp to a host frame below the entry — the
|
|
40
|
+
* conforming survival pattern), then deliver exactly once, then abort:
|
|
41
|
+
* before registration, or if the sink returns (the ruled host-contract
|
|
42
|
+
* violation). The address is the funnel frame's return address — the trap
|
|
43
|
+
* site — 0 where the toolchain cannot supply one. */
|
|
44
|
+
|
|
45
|
+
#if defined(__GNUC__) || defined(__clang__)
|
|
46
|
+
#define SCR_TRAP_ADDR() ((uint64_t)(uintptr_t)__builtin_return_address(0))
|
|
47
|
+
#else
|
|
48
|
+
#define SCR_TRAP_ADDR() ((uint64_t)0)
|
|
49
|
+
#endif
|
|
50
|
+
|
|
51
|
+
static _Noreturn void scr_library_trap_deliver(const char *msg, size_t len, uint64_t addr) {
|
|
52
|
+
scr_library_poisoned = true;
|
|
53
|
+
if (scr_library_sink != NULL) {
|
|
54
|
+
scr_library_sink(scr_library_sink_ctx, (const uint8_t *)msg, len, addr);
|
|
55
|
+
}
|
|
56
|
+
abort();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
__attribute__((noinline)) _Noreturn void scr_trap(const char *msg) {
|
|
60
|
+
scr_library_trap_deliver(msg, strlen(msg), SCR_TRAP_ADDR());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
__attribute__((noinline)) _Noreturn void scr_trap_fmt(const char *fmt, ...) {
|
|
64
|
+
static char buf[512]; /* no malloc on the invariant-failure path */
|
|
65
|
+
va_list ap;
|
|
66
|
+
va_start(ap, fmt);
|
|
67
|
+
int n = vsnprintf(buf, sizeof buf, fmt, ap);
|
|
68
|
+
va_end(ap);
|
|
69
|
+
size_t len = n < 0 ? 0 : (size_t)n >= sizeof buf ? sizeof buf - 1 : (size_t)n;
|
|
70
|
+
scr_library_trap_deliver(buf, len, SCR_TRAP_ADDR());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ── entry prologues ──────────────────────────────────────────────────── */
|
|
74
|
+
|
|
75
|
+
void scr_library_entry(bool reset_arena) {
|
|
76
|
+
/* A poisoned library's entries abort deterministically — never through the
|
|
77
|
+
* sink again (it received its exactly-once message when the trap fired),
|
|
78
|
+
* never into a heap whose invariants already failed. */
|
|
79
|
+
if (scr_library_poisoned) abort();
|
|
80
|
+
if (reset_arena) scr_library_arena_reset();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── the outbound-result arena ────────────────────────────────────────────
|
|
84
|
+
* Buffer-class results (string/bytes) MOVE in here; the host reads through
|
|
85
|
+
* borrowed pointers until the arena resets (per-entry under the auto
|
|
86
|
+
* posture, host-cycled when the profile declares a reset symbol; init and
|
|
87
|
+
* collect always reset). */
|
|
88
|
+
|
|
89
|
+
typedef struct {
|
|
90
|
+
void *v;
|
|
91
|
+
bool is_str; /* ScrStr vs ScrBytes — picks the release */
|
|
92
|
+
} ScrCoreArenaEnt;
|
|
93
|
+
|
|
94
|
+
static ScrCoreArenaEnt *scr_library_arena = NULL;
|
|
95
|
+
static size_t scr_library_arena_n = 0, scr_library_arena_cap = 0;
|
|
96
|
+
|
|
97
|
+
static void scr_library_arena_keep(void *v, bool is_str) {
|
|
98
|
+
if (scr_library_arena_n == scr_library_arena_cap) {
|
|
99
|
+
scr_library_arena_cap = scr_library_arena_cap ? scr_library_arena_cap * 2 : 16;
|
|
100
|
+
scr_library_arena = realloc(scr_library_arena, scr_library_arena_cap * sizeof *scr_library_arena);
|
|
101
|
+
if (!scr_library_arena) scr_trap("scriptc: out of memory\n");
|
|
102
|
+
}
|
|
103
|
+
scr_library_arena[scr_library_arena_n].v = v;
|
|
104
|
+
scr_library_arena[scr_library_arena_n].is_str = is_str;
|
|
105
|
+
scr_library_arena_n++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void scr_library_arena_reset(void) {
|
|
109
|
+
for (size_t i = 0; i < scr_library_arena_n; i++) {
|
|
110
|
+
if (scr_library_arena[i].is_str) scr_str_release((ScrStr *)scr_library_arena[i].v);
|
|
111
|
+
else scr_bytes_release((ScrBytes *)scr_library_arena[i].v);
|
|
112
|
+
}
|
|
113
|
+
scr_library_arena_n = 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
void scr_library_collect(void) {
|
|
117
|
+
scr_library_arena_reset();
|
|
118
|
+
scr_collect_cycles();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* ── marshalling helpers (both emissions call exactly these) ──────────── */
|
|
122
|
+
|
|
123
|
+
ScrStr *scr_library_str_in(const uint8_t *p, size_t len) {
|
|
124
|
+
/* ptr may be NULL when len is 0 (the contract's empty-buffer form). */
|
|
125
|
+
return scr_str_new(len == 0 ? "" : (const char *)p, len);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len) {
|
|
129
|
+
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)len);
|
|
130
|
+
if (b == NULL) {
|
|
131
|
+
/* scr_bytes_new throws only for lengths past 2^53-1 — an impossible
|
|
132
|
+
* host buffer; funnel the contract violation instead of a NULL deref. */
|
|
133
|
+
scr_exc_clear();
|
|
134
|
+
scr_trap("scriptc: library inbound bytes length out of range\n");
|
|
135
|
+
}
|
|
136
|
+
if (len > 0 && p != NULL) memcpy(b->data, p, len);
|
|
137
|
+
return b;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len) {
|
|
141
|
+
scr_library_arena_keep(s, true);
|
|
142
|
+
*out = (const uint8_t *)s->data; /* NUL-terminated after len (ScrStr layout) */
|
|
143
|
+
*out_len = s->len;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len) {
|
|
147
|
+
scr_library_arena_keep(b, false);
|
|
148
|
+
*out = b->data; /* elem is always u8 at the boundary (v1 bytes class) */
|
|
149
|
+
*out_len = b->len;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ── the reset registry + session reset ─────────────────────────────────
|
|
153
|
+
* Where an executable's always-linked units atexit() their lazy teardowns,
|
|
154
|
+
* library builds register here (scr_atexit in scr_runtime.h): registered
|
|
155
|
+
* once, drained on EVERY reset — re-registration guards in the units stay
|
|
156
|
+
* satisfied because the entry persists. */
|
|
157
|
+
|
|
158
|
+
#define SCR_LIB_MAX_RESETS 32
|
|
159
|
+
static void (*scr_library_resets[SCR_LIB_MAX_RESETS])(void);
|
|
160
|
+
static size_t scr_library_nresets = 0;
|
|
161
|
+
|
|
162
|
+
void scr_library_register_reset(void (*fn)(void)) {
|
|
163
|
+
for (size_t i = 0; i < scr_library_nresets; i++) {
|
|
164
|
+
if (scr_library_resets[i] == fn) return;
|
|
165
|
+
}
|
|
166
|
+
if (scr_library_nresets == SCR_LIB_MAX_RESETS) {
|
|
167
|
+
scr_trap("scriptc: internal error: library reset registry overflow\n");
|
|
168
|
+
}
|
|
169
|
+
scr_library_resets[scr_library_nresets++] = fn;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
#ifdef SCR_RC_AUDIT
|
|
173
|
+
extern long scr_str_live_count(void); /* scr_string.c */
|
|
174
|
+
extern long scr_arr_live_count(void); /* scr_array.c */
|
|
175
|
+
extern long scr_map_live_count(void); /* scr_map.c */
|
|
176
|
+
extern long scr_box_live_count(void); /* scr_closure.c */
|
|
177
|
+
extern long scr_closure_live_count(void); /* scr_closure.c */
|
|
178
|
+
extern long scr_obj_live_count(void); /* scr_object.c */
|
|
179
|
+
extern long scr_union_live_count(void); /* scr_union.c */
|
|
180
|
+
extern long scr_dyn_live_count(void); /* scr_json.c */
|
|
181
|
+
extern long scr_bytes_live_count(void); /* scr_bytes.c */
|
|
182
|
+
|
|
183
|
+
/* The per-session heap-emptiness assertion (the audit flavor's determinism
|
|
184
|
+
* seam): after a full reset the live counters must all read zero, or the
|
|
185
|
+
* previous session leaked. A failure is a TRAP through the sink — the
|
|
186
|
+
* executable audit's _Exit(99) stays exe-lane-only. */
|
|
187
|
+
static void scr_library_audit_zero(void) {
|
|
188
|
+
long strings = scr_str_live_count(), arrays = scr_arr_live_count(),
|
|
189
|
+
maps = scr_map_live_count(), boxes = scr_box_live_count(),
|
|
190
|
+
closures = scr_closure_live_count(), objects = scr_obj_live_count(),
|
|
191
|
+
unions = scr_union_live_count(), dyns = scr_dyn_live_count(),
|
|
192
|
+
bytes = scr_bytes_live_count();
|
|
193
|
+
if (strings != 0 || arrays != 0 || maps != 0 || boxes != 0 || closures != 0 ||
|
|
194
|
+
objects != 0 || unions != 0 || dyns != 0 || bytes != 0) {
|
|
195
|
+
scr_trap_fmt(
|
|
196
|
+
"scriptc LIBRARY RC AUDIT FAILED: %ld heap string(s), %ld array(s), "
|
|
197
|
+
"%ld map(s), %ld box(es), %ld closure(s), %ld object(s), "
|
|
198
|
+
"%ld union(s), %ld dyn value(s), %ld bytes value(s) live across re-init\n",
|
|
199
|
+
strings, arrays, maps, boxes, closures, objects, unions, dyns, bytes);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
#endif /* SCR_RC_AUDIT */
|
|
203
|
+
|
|
204
|
+
extern void scr_lib_session_cleanup(void); /* scr_lib.c: the interned process values */
|
|
205
|
+
|
|
206
|
+
void scr_library_reset(void) {
|
|
207
|
+
/* Called by the generated init entry AFTER the program TU released and
|
|
208
|
+
* zeroed its globals (run-once guards included) — the same order the
|
|
209
|
+
* executable exit path runs: library cleanup → cycle collection → RC
|
|
210
|
+
* audit. Everything here is re-runnable; the first call is a cheap
|
|
211
|
+
* no-op pass. */
|
|
212
|
+
scr_exc_clear();
|
|
213
|
+
scr_library_arena_reset();
|
|
214
|
+
for (size_t i = 0; i < scr_library_nresets; i++) scr_library_resets[i]();
|
|
215
|
+
scr_lib_session_cleanup();
|
|
216
|
+
scr_collect_cycles();
|
|
217
|
+
#ifdef SCR_RC_AUDIT
|
|
218
|
+
scr_library_audit_zero();
|
|
219
|
+
#endif
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#endif /* SCR_LIB */
|
package/src/scr_map.c
CHANGED
|
@@ -31,8 +31,7 @@ long scr_map_live_count(void) { return scr_live_maps; }
|
|
|
31
31
|
#define SCR_MAP_EMPTY SIZE_MAX
|
|
32
32
|
|
|
33
33
|
static void scr_map_oom(void) {
|
|
34
|
-
|
|
35
|
-
abort();
|
|
34
|
+
scr_trap("scriptc: out of memory\n");
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/* ── key normalization + hashing (SameValueZero) ───────────────────────
|
|
@@ -462,8 +461,7 @@ bool scr_map_iter_live(const ScrMap *m, double i) {
|
|
|
462
461
|
|
|
463
462
|
static const ScrMapEntry *scr_map_iter_at(const ScrMap *m, double i) {
|
|
464
463
|
if (!(i >= 0) || i >= (double)m->nentries || !m->entries[(size_t)i].live) {
|
|
465
|
-
|
|
466
|
-
abort();
|
|
464
|
+
scr_trap("scriptc: internal error: map iteration index out of range\n");
|
|
467
465
|
}
|
|
468
466
|
return &m->entries[(size_t)i];
|
|
469
467
|
}
|
package/src/scr_path.c
CHANGED
|
@@ -37,8 +37,7 @@ static void pb_init(PathBuf *b) {
|
|
|
37
37
|
b->len = 0;
|
|
38
38
|
b->data = malloc(b->cap);
|
|
39
39
|
if (!b->data) {
|
|
40
|
-
|
|
41
|
-
abort();
|
|
40
|
+
scr_trap("scriptc: out of memory\n");
|
|
42
41
|
}
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -47,8 +46,7 @@ static void pb_reserve(PathBuf *b, size_t extra) {
|
|
|
47
46
|
while (b->len + extra > b->cap) b->cap *= 2;
|
|
48
47
|
char *grown = realloc(b->data, b->cap);
|
|
49
48
|
if (!grown) {
|
|
50
|
-
|
|
51
|
-
abort();
|
|
49
|
+
scr_trap("scriptc: out of memory\n");
|
|
52
50
|
}
|
|
53
51
|
b->data = grown;
|
|
54
52
|
}
|
|
@@ -226,8 +224,7 @@ ScrStr *scr_path_join(ScrArr *parts) {
|
|
|
226
224
|
static void scr_path_cwd(PathBuf *out) {
|
|
227
225
|
char buf[4096];
|
|
228
226
|
if (!getcwd(buf, sizeof buf)) {
|
|
229
|
-
|
|
230
|
-
abort();
|
|
227
|
+
scr_trap("scriptc: path.resolve: getcwd failed\n");
|
|
231
228
|
}
|
|
232
229
|
pb_append(out, buf, strlen(buf));
|
|
233
230
|
}
|
|
@@ -984,8 +981,7 @@ ScrStr *scr_path_win32_relative(ScrStr *from, ScrStr *to) {
|
|
|
984
981
|
char *flow = malloc(rfrom->len ? rfrom->len : 1);
|
|
985
982
|
char *tlow = malloc(rto->len ? rto->len : 1);
|
|
986
983
|
if (!flow || !tlow) {
|
|
987
|
-
|
|
988
|
-
abort();
|
|
984
|
+
scr_trap("scriptc: out of memory\n");
|
|
989
985
|
}
|
|
990
986
|
for (size_t k = 0; k < rfrom->len; k++) flow[k] = scr_path_w32_lower(rfrom->data[k]);
|
|
991
987
|
for (size_t k = 0; k < rto->len; k++) tlow[k] = scr_path_w32_lower(rto->data[k]);
|
package/src/scr_regex.c
CHANGED
|
@@ -37,8 +37,7 @@
|
|
|
37
37
|
#include "libregexp.h"
|
|
38
38
|
|
|
39
39
|
static void scr_regex_oom(void) {
|
|
40
|
-
|
|
41
|
-
abort();
|
|
40
|
+
scr_trap("scriptc: out of memory\n");
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
/* ── libregexp host hooks ─────────────────────────────────────────────
|
|
@@ -102,7 +101,7 @@ static void scr_note_compiled(ScrRegex *re) {
|
|
|
102
101
|
scr_compiled = realloc(scr_compiled, scr_compiled_cap * sizeof *scr_compiled);
|
|
103
102
|
if (!scr_compiled) scr_regex_oom();
|
|
104
103
|
}
|
|
105
|
-
if (scr_compiled_len == 0)
|
|
104
|
+
if (scr_compiled_len == 0) scr_atexit(scr_regex_free_bytecodes);
|
|
106
105
|
scr_compiled[scr_compiled_len++] = re;
|
|
107
106
|
}
|
|
108
107
|
|
|
@@ -119,9 +118,8 @@ static int scr_lre_flags(const ScrStr *flags) {
|
|
|
119
118
|
case 'u': mask |= LRE_FLAG_UNICODE; break;
|
|
120
119
|
case 'y': mask |= LRE_FLAG_STICKY; break;
|
|
121
120
|
default:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
abort();
|
|
121
|
+
scr_trap_fmt("scriptc: internal error: unexpected regex flag '%c'\n",
|
|
122
|
+
flags->data[i]);
|
|
125
123
|
}
|
|
126
124
|
}
|
|
127
125
|
return mask;
|
|
@@ -197,9 +195,8 @@ static uint8_t *scr_regex_bc(ScrRegex *re) {
|
|
|
197
195
|
* tsc's parser has already caught plain syntax errors, so this is
|
|
198
196
|
* rare). */
|
|
199
197
|
fflush(stdout);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
abort();
|
|
198
|
+
scr_trap_fmt("scriptc: SyntaxError: Invalid regular expression: /%s/%s: %s\n",
|
|
199
|
+
re->source->data, re->flags->data, error_msg);
|
|
203
200
|
}
|
|
204
201
|
re->bc = bc;
|
|
205
202
|
scr_note_compiled(re);
|
|
@@ -315,8 +312,7 @@ static int scr_exec(uint8_t **capture, const uint8_t *bc, const uint16_t *u,
|
|
|
315
312
|
int rc = lre_exec(capture, bc, (const uint8_t *)u, index, len, 1, lre_opaque());
|
|
316
313
|
if (rc < 0) {
|
|
317
314
|
fflush(stdout);
|
|
318
|
-
|
|
319
|
-
abort();
|
|
315
|
+
scr_trap("scriptc: regular expression execution failed\n");
|
|
320
316
|
}
|
|
321
317
|
return rc;
|
|
322
318
|
}
|
|
@@ -351,11 +347,9 @@ bool scr_regex_test(ScrRegex *re, ScrStr *s) {
|
|
|
351
347
|
* iteration this slice does not model (the frontend rejects the sites
|
|
352
348
|
* it can see; values that flow through variables land here). */
|
|
353
349
|
fflush(stdout);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
"replace/replaceAll/split\n");
|
|
358
|
-
abort();
|
|
350
|
+
scr_trap("scriptc: test() on a regex with the 'g' or 'y' flag is not "
|
|
351
|
+
"supported (stateful lastIndex); drop the flag, or use "
|
|
352
|
+
"replace/replaceAll/split\n");
|
|
359
353
|
}
|
|
360
354
|
int len;
|
|
361
355
|
uint16_t *u = scr_to_utf16(s, &len);
|
|
@@ -379,11 +373,9 @@ ScrArr *scr_regex_match(ScrStr *s, ScrRegex *re) {
|
|
|
379
373
|
uint8_t *bc = scr_regex_bc(re);
|
|
380
374
|
if (lre_get_flags(bc) & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) {
|
|
381
375
|
fflush(stdout);
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
"flag, or use replace/replaceAll/split\n");
|
|
386
|
-
abort();
|
|
376
|
+
scr_trap("scriptc: match() on a regex with the 'g' or 'y' flag is not "
|
|
377
|
+
"supported (an every-match array is a different shape); drop the "
|
|
378
|
+
"flag, or use replace/replaceAll/split\n");
|
|
387
379
|
}
|
|
388
380
|
int len;
|
|
389
381
|
uint16_t *u = scr_to_utf16(s, &len);
|
|
@@ -803,7 +795,7 @@ static bool scr_assert_regex_hits(ScrRegex *re, ScrStr *s) {
|
|
|
803
795
|
static ScrStr *scr_assert_regex_render(ScrRegex *re) {
|
|
804
796
|
size_t cap = re->source->len + re->flags->len + 2;
|
|
805
797
|
char *buf = malloc(cap);
|
|
806
|
-
if (!buf)
|
|
798
|
+
if (!buf) scr_trap("scriptc: out of memory\n");
|
|
807
799
|
size_t n = 0;
|
|
808
800
|
buf[n++] = '/';
|
|
809
801
|
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
|
|
|
831
823
|
const char *mid = ". Input:\n\n";
|
|
832
824
|
size_t cap = strlen(head) + rre->len + strlen(mid) + insp->len + 1;
|
|
833
825
|
char *buf = malloc(cap);
|
|
834
|
-
if (!buf)
|
|
826
|
+
if (!buf) scr_trap("scriptc: out of memory\n");
|
|
835
827
|
size_t n = 0;
|
|
836
828
|
memcpy(buf + n, head, strlen(head));
|
|
837
829
|
n += strlen(head);
|
|
@@ -924,8 +916,7 @@ ScrRegex *scr_regex_new(ScrStr *pattern, ScrStr *flags) {
|
|
|
924
916
|
}
|
|
925
917
|
ScrRegex *re = calloc(1, sizeof *re);
|
|
926
918
|
if (!re) {
|
|
927
|
-
|
|
928
|
-
abort();
|
|
919
|
+
scr_trap("scriptc: out of memory\n");
|
|
929
920
|
}
|
|
930
921
|
re->rc = 1;
|
|
931
922
|
re->source = pattern->len > 0 ? scr_str_retain(pattern) : scr_str_new("(?:)", 4);
|
package/src/scr_runtime.h
CHANGED
|
@@ -39,6 +39,81 @@ char *strcasestr(const char *hay, const char *needle);
|
|
|
39
39
|
* flush-at-exit, RC audit registration (when built with -DSCR_RC_AUDIT). */
|
|
40
40
|
void scr_init(void);
|
|
41
41
|
|
|
42
|
+
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
|
|
43
|
+
* Every unrecoverable runtime trap — OOM, semantic range traps, internal-
|
|
44
|
+
* invariant failures — funnels through this pair instead of open-coded
|
|
45
|
+
* fputs/fprintf + abort. Executable builds expand to exactly the historical
|
|
46
|
+
* behavior (the message's bytes on stderr, then abort — the default lane
|
|
47
|
+
* must not change by a byte). Library builds (-DSCR_LIB) route the message to
|
|
48
|
+
* the host-registered panic sink and abort only as the last resort: before
|
|
49
|
+
* registration, or if the sink returns (the ruled host-contract violation —
|
|
50
|
+
* a conforming sink longjmps to a host frame BELOW the entry, never back
|
|
51
|
+
* into library frames). Messages keep their trailing newline in both lanes so
|
|
52
|
+
* the funnel is a pure indirection over identical bytes. */
|
|
53
|
+
_Noreturn void scr_trap(const char *msg);
|
|
54
|
+
_Noreturn void scr_trap_fmt(const char *fmt, ...);
|
|
55
|
+
|
|
56
|
+
/* ── library mode (scr_library.c, linked only into library artifacts) ─────
|
|
57
|
+
* A library artifact has no main, no event loop, no signal handlers, no
|
|
58
|
+
* atexit registrations, and never touches host stdio modes or buffering:
|
|
59
|
+
* initialization runs inside the profile-named init entry (re-runnable
|
|
60
|
+
* deterministically), traps route to the sink above, and buffer-class
|
|
61
|
+
* results live in a library-owned result arena. Everything here compiles only
|
|
62
|
+
* under -DSCR_LIB; executable builds never contain it. */
|
|
63
|
+
#ifdef SCR_LIB
|
|
64
|
+
typedef struct ScrStr ScrStr; /* full definitions below (C11 repeat) */
|
|
65
|
+
typedef struct ScrBytes ScrBytes;
|
|
66
|
+
/* The host's panic sink: msg is UTF-8, valid only for the duration of the
|
|
67
|
+
* call; address is the trap site's return address (0 when the toolchain
|
|
68
|
+
* cannot supply one); ctx is the registration's opaque pointer. The sink
|
|
69
|
+
* must not call back into any library entry and must not unwind or longjmp
|
|
70
|
+
* back into library frames. */
|
|
71
|
+
typedef void (*ScrLibSinkFn)(void *ctx, const uint8_t *msg, size_t msg_len,
|
|
72
|
+
uint64_t address);
|
|
73
|
+
void scr_library_set_sink(ScrLibSinkFn fn, void *ctx); /* latest wins */
|
|
74
|
+
|
|
75
|
+
/* Entry prologue: aborts deterministically when the library is poisoned (a
|
|
76
|
+
* trap already fired — no profile entry may run again; recovery is process
|
|
77
|
+
* restart). reset_arena additionally drops the result arena (the
|
|
78
|
+
* auto-reset posture, and the reset/collect entries' shared body). */
|
|
79
|
+
void scr_library_entry(bool reset_arena);
|
|
80
|
+
void scr_library_arena_reset(void);
|
|
81
|
+
/* The mode-provided collect entry's body: arena reset + a full cycle
|
|
82
|
+
* collection (snapshot-invariant by construction — collection frees only
|
|
83
|
+
* unreachable cycles). */
|
|
84
|
+
void scr_library_collect(void);
|
|
85
|
+
|
|
86
|
+
/* Full session reset, called by the generated init entry AFTER the program
|
|
87
|
+
* TU released and zeroed its globals: pending-exception clear, arena
|
|
88
|
+
* reset, the reset registry's drains (the units' repointed atexit halves),
|
|
89
|
+
* the library's interned process values, a cycle collection, and — under
|
|
90
|
+
* SCR_RC_AUDIT — the zero-live-heap assertion (a failure is a trap through
|
|
91
|
+
* the sink, never _Exit). */
|
|
92
|
+
void scr_library_reset(void);
|
|
93
|
+
/* Where a unit would atexit() a lazy teardown, library builds register it
|
|
94
|
+
* here instead (called on every scr_library_reset, registered once). */
|
|
95
|
+
void scr_library_register_reset(void (*fn)(void));
|
|
96
|
+
/* An escaped exception at an entry boundary: renders the same "Uncaught
|
|
97
|
+
* ..." text the executable epilogue prints, releases the payload, and
|
|
98
|
+
* routes the text through the trap funnel. No-op when nothing is pending.
|
|
99
|
+
* Defined in scr_exception.c (it owns the cell). */
|
|
100
|
+
void scr_library_check_exc(void);
|
|
101
|
+
|
|
102
|
+
/* Marshalling helpers the generated wrappers call (both emissions share
|
|
103
|
+
* these bodies, which is how the two lanes stay identical by
|
|
104
|
+
* construction). Inbound is borrowed-and-copied; outbound values MOVE into
|
|
105
|
+
* the result arena and stay valid until the next arena reset. String
|
|
106
|
+
* results are NUL-terminated after *out_len bytes (ScrStr's layout). */
|
|
107
|
+
ScrStr *scr_library_str_in(const uint8_t *p, size_t len); /* +1 */
|
|
108
|
+
ScrBytes *scr_library_bytes_in(const uint8_t *p, size_t len); /* +1, u8 */
|
|
109
|
+
void scr_library_str_out(ScrStr *s, const uint8_t **out, size_t *out_len);
|
|
110
|
+
void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
|
|
111
|
+
|
|
112
|
+
#define scr_atexit(fn) scr_library_register_reset(fn)
|
|
113
|
+
#else
|
|
114
|
+
#define scr_atexit(fn) atexit(fn)
|
|
115
|
+
#endif /* SCR_LIB */
|
|
116
|
+
|
|
42
117
|
/* ── cycle collection (scr_cycle.c) ───────────────────────────────────
|
|
43
118
|
* Reference counting alone cannot free cycles, so every object that can
|
|
44
119
|
* participate in one — capture boxes, heap closures, unions, promises, and
|
|
@@ -2842,14 +2917,48 @@ extern void scr_dyn_listener_fire_err(ScrClosure *cb, ScrStr *msg);
|
|
|
2842
2917
|
|
|
2843
2918
|
/* Output buffer for the compiler-emitted type-directed JSON serializers.
|
|
2844
2919
|
* Value-typed and stack-allocated by the emitted code; _finish hands the
|
|
2845
|
-
* bytes over as a +1 ScrStr and frees the buffer storage
|
|
2920
|
+
* bytes over as a +1 ScrStr and frees the buffer storage (including the
|
|
2921
|
+
* circular-detection stack below). */
|
|
2922
|
+
struct ScrJsonSeenEnt;
|
|
2846
2923
|
typedef struct {
|
|
2847
2924
|
char *data;
|
|
2848
2925
|
size_t len;
|
|
2849
2926
|
size_t cap;
|
|
2927
|
+
/* Circular-structure detection for RECURSIVE record types (cyclic
|
|
2928
|
+
* values must throw Node's exact TypeError, never recurse forever):
|
|
2929
|
+
* the stack of container values currently being serialized, each with
|
|
2930
|
+
* its outgoing edge label (the member being written). Only walkers over
|
|
2931
|
+
* cycle-CAPABLE types (the collector-fixpoint set) maintain it; acyclic
|
|
2932
|
+
* types keep the zero-cost path. */
|
|
2933
|
+
struct ScrJsonSeenEnt *seen;
|
|
2934
|
+
size_t seen_len;
|
|
2935
|
+
size_t seen_cap;
|
|
2850
2936
|
} ScrJsonBuf;
|
|
2851
2937
|
|
|
2852
2938
|
void scr_jb_init(ScrJsonBuf *b);
|
|
2939
|
+
/* Push a container onto the circular-detection stack before serializing
|
|
2940
|
+
* its members. If `v` is already ON the stack, throws V8's exact
|
|
2941
|
+
* "Converting circular structure to JSON" TypeError (the --> starting at /
|
|
2942
|
+
* |property/index hops/--- closes the circle rendering, ellipsis rules
|
|
2943
|
+
* included) and returns false — the caller returns immediately; the
|
|
2944
|
+
* emitted stringify site runs the pending check. `is_array` picks the
|
|
2945
|
+
* constructor name in the message ('Array' for arrays AND tuple shapes —
|
|
2946
|
+
* their JS values are arrays — 'Object' for records). */
|
|
2947
|
+
bool scr_jb_enter(ScrJsonBuf *b, const void *v, bool is_array);
|
|
2948
|
+
void scr_jb_leave(ScrJsonBuf *b);
|
|
2949
|
+
/* Record the edge currently being serialized on the stack top: a static
|
|
2950
|
+
* property name (emitted C literal), an overflow key (borrowed for the
|
|
2951
|
+
* duration of the member write), or an array/tuple index. */
|
|
2952
|
+
void scr_jb_edge_prop(ScrJsonBuf *b, const char *name);
|
|
2953
|
+
void scr_jb_edge_key(ScrJsonBuf *b, const ScrStr *key);
|
|
2954
|
+
void scr_jb_edge_idx(ScrJsonBuf *b, size_t i);
|
|
2955
|
+
|
|
2956
|
+
/* Circular guard for the compiler-emitted typed→dyn converters (sc_td_*
|
|
2957
|
+
* over cycle-capable containers): enter TRAPS on a value already being
|
|
2958
|
+
* converted (a cyclic value has no finite DOM copy — Node shares the
|
|
2959
|
+
* reference instead; SEMANTICS.md), else pushes. */
|
|
2960
|
+
void scr_dyn_from_enter(const void *v);
|
|
2961
|
+
void scr_dyn_from_leave(void);
|
|
2853
2962
|
void scr_jb_putc(ScrJsonBuf *b, char c);
|
|
2854
2963
|
void scr_jb_puts(ScrJsonBuf *b, const char *s);
|
|
2855
2964
|
/* JS JSON.stringify number rules: NaN/±Infinity → null, -0 → 0, else
|
|
@@ -4020,6 +4129,18 @@ ScrStr *scr_insp_jsval(ScrJsval *v, double recurse, double depth);
|
|
|
4020
4129
|
#endif
|
|
4021
4130
|
void scr_insp_begin(double recurse);
|
|
4022
4131
|
void scr_insp_entry(ScrStr *s, bool is_num);
|
|
4132
|
+
/* Circular references, Node-exact (<ref *N> / [Circular *N]): the
|
|
4133
|
+
* compiler-emitted helpers over CYCLE-CAPABLE composites call circ_check
|
|
4134
|
+
* FIRST (a value already on the traversal stack answers its circular id,
|
|
4135
|
+
* assigned in discovery order — the caller renders scr_insp_circular and
|
|
4136
|
+
* descends no further), seen_push after begin, and ref_wrap around end's
|
|
4137
|
+
* result (pops the stack; values in the circular map gain the "<ref *N> "
|
|
4138
|
+
* prefix). State resets at each top-level value's first frame
|
|
4139
|
+
* (scr_insp_begin(1)). ref_wrap BORROWS s and returns +1. */
|
|
4140
|
+
double scr_insp_circ_check(const void *v);
|
|
4141
|
+
void scr_insp_seen_push(const void *v);
|
|
4142
|
+
ScrStr *scr_insp_circular(double id);
|
|
4143
|
+
ScrStr *scr_insp_ref_wrap(const void *v, ScrStr *s);
|
|
4023
4144
|
ScrStr *scr_insp_more_items(double remaining); /* "... N more items" */
|
|
4024
4145
|
ScrStr *scr_insp_key(ScrStr *k); /* bare-or-quoted property-name ladder; +1 */
|
|
4025
4146
|
ScrStr *scr_insp_end(ScrStr *base, ScrStr *b0, ScrStr *b1, double recurse,
|
|
@@ -4964,6 +5085,11 @@ void scr_assert_fail_msg(ScrStr *message); /* takes ownership; always throws */
|
|
|
4964
5085
|
ScrStr *scr_assert_inspect_str(const ScrStr *s); /* util.inspect quoting; +1 */
|
|
4965
5086
|
void scr_assert_ok(bool pass, ScrStr *message);
|
|
4966
5087
|
bool scr_assert_same_value_f64(double a, double b); /* Object.is */
|
|
5088
|
+
/* deepStrictEqual over cyclic values: pair memo (enter answers true for
|
|
5089
|
+
* a pair already being compared — Node's coinductive memo; leave pops).
|
|
5090
|
+
* The compiler-emitted helpers over cycle-capable types wrap with these. */
|
|
5091
|
+
bool scr_assert_deq_enter(const void *a, const void *b);
|
|
5092
|
+
void scr_assert_deq_leave(void);
|
|
4967
5093
|
void scr_assert_eq_f64(double a, double b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
|
4968
5094
|
void scr_assert_eq_str(ScrStr *a, ScrStr *b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
|
4969
5095
|
void scr_assert_eq_bool(bool a, bool b, bool negated, bool deep, ScrStr *msg, bool has_msg);
|
package/src/scr_string.c
CHANGED
|
@@ -15,8 +15,7 @@ long scr_str_live_count(void) { return scr_live_strings; }
|
|
|
15
15
|
#endif
|
|
16
16
|
|
|
17
17
|
static void scr_oom(void) {
|
|
18
|
-
|
|
19
|
-
abort();
|
|
18
|
+
scr_trap("scriptc: out of memory\n");
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/* ── UTF-16 index cache ───────────────────────────────────────────────
|
|
@@ -519,8 +518,7 @@ ScrStr *scr_str_slice(ScrStr *s, double start, double end) {
|
|
|
519
518
|
ScrStr *scr_str_repeat(ScrStr *s, double count) {
|
|
520
519
|
double n = scr_to_integer_or_infinity(count);
|
|
521
520
|
if (n < 0 || (isinf(n) && n > 0)) {
|
|
522
|
-
|
|
523
|
-
abort();
|
|
521
|
+
scr_trap("scriptc: RangeError: Invalid count value\n");
|
|
524
522
|
}
|
|
525
523
|
if (n == 0 || s->len == 0) return scr_str_empty();
|
|
526
524
|
/* n is a finite non-negative integer here. Reject sizes malloc could not
|
package/src/scr_symbol.c
CHANGED
|
@@ -44,8 +44,7 @@ void scr_sym_release_v(void *p) { scr_sym_release(p); }
|
|
|
44
44
|
ScrSym *scr_sym_new(ScrStr *desc) {
|
|
45
45
|
ScrSym *s = malloc(sizeof(ScrSym));
|
|
46
46
|
if (!s) {
|
|
47
|
-
|
|
48
|
-
abort();
|
|
47
|
+
scr_trap("scriptc: out of memory\n");
|
|
49
48
|
}
|
|
50
49
|
s->rc = 1;
|
|
51
50
|
s->desc = desc ? scr_str_retain(desc) : NULL;
|
|
@@ -75,7 +74,7 @@ ScrSym *scr_sym_for(ScrStr *key) {
|
|
|
75
74
|
/* First request for this key: a fresh symbol whose description IS the
|
|
76
75
|
* key (the spec's Symbol.for behavior), chained into the registry with
|
|
77
76
|
* the registry's own reference. */
|
|
78
|
-
if (!g_sym_registry)
|
|
77
|
+
if (!g_sym_registry) scr_atexit(scr_sym_registry_cleanup);
|
|
79
78
|
ScrSym *s = scr_sym_new(key);
|
|
80
79
|
s->reg_key = scr_str_retain(key);
|
|
81
80
|
s->reg_next = g_sym_registry;
|