@scriptc/runtime 0.0.11 → 0.0.13
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_assert.c +13 -13
- package/src/scr_async.c +53 -8
- package/src/scr_async_dyn.c +163 -47
- package/src/scr_bytes_io.c +5 -5
- package/src/scr_dc.c +8 -8
- package/src/scr_dgram.c +1 -1
- package/src/scr_dyn_handle.c +5 -5
- package/src/scr_dyn_invoke.c +42 -18
- package/src/scr_error.c +3 -3
- package/src/scr_events_emitter.c +1 -1
- package/src/scr_http.c +1080 -34
- package/src/scr_http2.c +53 -3
- package/src/scr_inspect.c +3 -3
- package/src/scr_island.c +134 -25
- package/src/scr_json.c +124 -68
- package/src/scr_net.c +254 -35
- package/src/scr_qs.c +1 -1
- package/src/scr_runtime.h +278 -124
- package/src/scr_stream.c +4 -4
- package/src/scr_tls.c +42 -5
- package/src/scr_tls_ca.c +288 -0
- package/src/scr_web.c +1 -1
package/package.json
CHANGED
package/src/scr_assert.c
CHANGED
|
@@ -396,15 +396,15 @@ void scr_assert_ref_eq_fn(const ScrClosure *a, const ScrClosure *b, bool negated
|
|
|
396
396
|
}
|
|
397
397
|
|
|
398
398
|
/* ── strictEqual / deepStrictEqual over CHECKED-DYNAMIC (dyn) operands ──
|
|
399
|
-
* The
|
|
399
|
+
* The checked-dynamic tree carries the value's kind at runtime, so one entry point serves
|
|
400
400
|
* the whole quartet: SameValue for the strict pair (numbers by Object.is,
|
|
401
401
|
* strings by bytes, units by kind, arrays/objects/bytes by node identity,
|
|
402
402
|
* functions by the BOXED CLOSURE — two dyn crossings of one function are
|
|
403
|
-
* the same JS function), a structural
|
|
403
|
+
* the same JS function), a structural dyn walk for the deep pair.
|
|
404
404
|
*
|
|
405
405
|
* Failure messages reproduce Node's assertion_error.js against the v24
|
|
406
406
|
* sources: inspectValue is a compact:false / sorted:true / depth-1000
|
|
407
|
-
* rendering of the
|
|
407
|
+
* rendering of the checked-dynamic tree (each entry on its own line, entries sorted by
|
|
408
408
|
* their RENDERED text — Node's `sorted: true` sorts formatted entries),
|
|
409
409
|
* the simple/stacked scalar forms match the static paths byte-for-byte,
|
|
410
410
|
* and composite diffs run the real myers line diff with Node's printer
|
|
@@ -415,7 +415,7 @@ void scr_assert_ref_eq_fn(const ScrClosure *a, const ScrClosure *b, bool negated
|
|
|
415
415
|
* the [Function: name] form, and values rendering past ~4096 lines fall
|
|
416
416
|
* back to the whole-value +/- form without context collapsing. */
|
|
417
417
|
|
|
418
|
-
/* JS SameValue over two
|
|
418
|
+
/* JS SameValue over two dyn values (Object.is — the strictEqual and
|
|
419
419
|
* notStrictEqual comparison). Functions compare by boxed closure: the
|
|
420
420
|
* ScrDyn box is a boundary artifact, the closure IS the JS identity. */
|
|
421
421
|
static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
|
|
@@ -449,19 +449,19 @@ static bool scr_assert_dyn_same_value(const ScrDyn *a, const ScrDyn *b) {
|
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
-
/* Node's strict deep equality over two
|
|
452
|
+
/* Node's strict deep equality over two dyn values: kind-wise — Object.is
|
|
453
453
|
* numbers, byte-equal strings, units by kind, per-element arrays,
|
|
454
|
-
* key-set-plus-values objects (
|
|
454
|
+
* key-set-plus-values objects (dyn keys are unique, so equal lengths and
|
|
455
455
|
* an a⊆b value walk prove the bijection), brand-aware bytes (the buffer
|
|
456
456
|
* flavor bit IS the Buffer-vs-Uint8Array prototype Node compares first),
|
|
457
457
|
* reference identity for functions (boxed closure). Plain recursion:
|
|
458
|
-
* JSON-origin
|
|
458
|
+
* JSON-origin dyn values are trees; a keyed-write cycle (h.self = h) has no
|
|
459
459
|
* memo here where Node carries one (documented divergence). */
|
|
460
460
|
static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
|
|
461
461
|
if (a == b) return true;
|
|
462
462
|
if (a->kind != b->kind) {
|
|
463
463
|
/* A MIXED comparison with an island side (wrapped engine object vs
|
|
464
|
-
*
|
|
464
|
+
* dyn data): Node walks both structurally — a plain `false` would
|
|
465
465
|
* mint a fabricated AssertionError for values Node may call equal.
|
|
466
466
|
* Loud fence (the long-tail lane's structural walk). */
|
|
467
467
|
if (a->kind == SCR_DYN_JSVAL || b->kind == SCR_DYN_JSVAL) {
|
|
@@ -528,7 +528,7 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
|
|
|
528
528
|
return false; /* unreachable */
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
/* ── inspectValue over the
|
|
531
|
+
/* ── inspectValue over the checked-dynamic tree (assertion_error.js's inspect options:
|
|
532
532
|
* compact:false, sorted:true, depth 1000, maxArrayLength Infinity) ───── */
|
|
533
533
|
|
|
534
534
|
#define SCR_ASSERT_CF_DEPTH 1000
|
|
@@ -924,7 +924,7 @@ static bool scr_assert_print_myers(ScrAssertBuf *b, const ScrDiffOp *diff, size_
|
|
|
924
924
|
|
|
925
925
|
/* ── the dyn entry point ─────────────────────────────────────────────── */
|
|
926
926
|
|
|
927
|
-
/* Is this
|
|
927
|
+
/* Is this dyn kind `typeof == "object" && != null` to assertion_error.js? */
|
|
928
928
|
static bool scr_assert_dyn_is_object(const ScrDyn *d) {
|
|
929
929
|
return d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ || d->kind == SCR_DYN_BYTES ||
|
|
930
930
|
d->kind == SCR_DYN_HANDLE || d->kind == SCR_DYN_PROMISE ||
|
|
@@ -1095,7 +1095,7 @@ static void scr_assert_dyn_neq_fail(ScrDyn *a, bool deep, ScrStr *msg, bool has_
|
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
1097
|
/* strictEqual / notStrictEqual / deepStrictEqual / notDeepStrictEqual
|
|
1098
|
-
* where either operand is a checked-dynamic value (both arrive as
|
|
1098
|
+
* where either operand is a checked-dynamic value (both arrive as dyn
|
|
1099
1099
|
* values — the frontend boxes a static side). Borrows everything. */
|
|
1100
1100
|
void scr_assert_eq_dyn(ScrDyn *a, ScrDyn *b, bool negated, bool deep,
|
|
1101
1101
|
ScrStr *msg, bool has_msg) {
|
|
@@ -1113,7 +1113,7 @@ void scr_assert_eq_dyn(ScrDyn *a, ScrDyn *b, bool negated, bool deep,
|
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
1115
|
/* Node's expectsError over an error-INSTANCE expected (assert.throws/
|
|
1116
|
-
* rejects second argument): every key of the expected
|
|
1116
|
+
* rejects second argument): every key of the expected dyn error (the
|
|
1117
1117
|
* %error marker skipped; name/message/code is the encoding's surface)
|
|
1118
1118
|
* must deep-strict-equal the caught value's — extra ACTUAL keys are fine
|
|
1119
1119
|
* (Node walks the expected's keys only). A mismatch fails through the
|
|
@@ -1441,7 +1441,7 @@ void scr_assert_iferror_bool(bool v) {
|
|
|
1441
1441
|
}
|
|
1442
1442
|
|
|
1443
1443
|
/* The checked-dynamic argument (test/common's mustSucceed wrapper): the
|
|
1444
|
-
*
|
|
1444
|
+
* dyn kind dispatches — units pass quietly, %error-marked objects (the
|
|
1445
1445
|
* caughtToDyn encoding) throw with the error's message (its name when
|
|
1446
1446
|
* the message is empty, Node's rule), everything else throws with the
|
|
1447
1447
|
* value's inspection. */
|
package/src/scr_async.c
CHANGED
|
@@ -106,6 +106,10 @@ struct ScrPromise {
|
|
|
106
106
|
size_t ncbs, cbs_cap;
|
|
107
107
|
/* Unhandled-rejection tracking: set when rejected, cleared on await. */
|
|
108
108
|
bool rejection_observed;
|
|
109
|
+
/* Set when the loop-end report delivered THIS promise to
|
|
110
|
+
* 'unhandledRejection' listeners — a handler attached after that is
|
|
111
|
+
* Node's 'rejectionHandled' moment (scr_prom_observe below). */
|
|
112
|
+
bool reported_unhandled;
|
|
109
113
|
};
|
|
110
114
|
|
|
111
115
|
#ifdef SCR_RC_AUDIT
|
|
@@ -187,6 +191,35 @@ ScrPromise *scr_promise_new(void) {
|
|
|
187
191
|
return p;
|
|
188
192
|
}
|
|
189
193
|
|
|
194
|
+
/* The 'rejectionHandled' hook (scr_async_dyn.c installs it at listener
|
|
195
|
+
* registration — the scr_urj_deliver_fn pattern, so listener-free
|
|
196
|
+
* binaries keep their size class): called when a promise the loop-end
|
|
197
|
+
* report already delivered as unhandled gains a handler. */
|
|
198
|
+
void (*scr_rjh_notify_fn)(ScrPromise *p) = NULL;
|
|
199
|
+
|
|
200
|
+
/* Every handler attach funnels here: mark the rejection observed, and
|
|
201
|
+
* fire Node's 'rejectionHandled' when the attach arrived AFTER the
|
|
202
|
+
* report delivered this promise to 'unhandledRejection' listeners (the
|
|
203
|
+
* model's one late-handling window — earlier handling keeps the promise
|
|
204
|
+
* out of the report entirely). The flag clears on the first attach, so
|
|
205
|
+
* one report fires at most one 'rejectionHandled' — Node's pairing. */
|
|
206
|
+
static void scr_prom_observe(ScrPromise *p) {
|
|
207
|
+
p->rejection_observed = true;
|
|
208
|
+
if (p->reported_unhandled) {
|
|
209
|
+
p->reported_unhandled = false;
|
|
210
|
+
if (scr_rjh_notify_fn != NULL) scr_rjh_notify_fn(p);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* The attach-time handled mark (scr_async_dyn.c's dyn then/catch with a
|
|
215
|
+
* rejection handler): Node marks a rejection handled at ATTACH, not when
|
|
216
|
+
* the reaction runs — and the loop-exhaustion window (a .catch inside an
|
|
217
|
+
* 'unhandledRejection' listener) has no later fiber turn whose await
|
|
218
|
+
* could observe it. */
|
|
219
|
+
void scr_promise_mark_handled(ScrPromise *p) {
|
|
220
|
+
if (p->state == SCR_PROM_REJECTED) scr_prom_observe(p);
|
|
221
|
+
}
|
|
222
|
+
|
|
190
223
|
ScrPromise *scr_promise_retain(ScrPromise *p) {
|
|
191
224
|
if (p && p->rc != SIZE_MAX) {
|
|
192
225
|
p->rc++;
|
|
@@ -272,7 +305,7 @@ struct ScrFiber {
|
|
|
272
305
|
|
|
273
306
|
/* ── AsyncLocalStorage (node:async_hooks) ─────────────────────────────
|
|
274
307
|
* Stores are process-lived ids; the CONTEXT is an immutable refcounted
|
|
275
|
-
* snapshot of (store id →
|
|
308
|
+
* snapshot of (store id → dyn value) entries. One ACTIVE SLOT pointer
|
|
276
309
|
* (the exc-cell pattern): main owns a static slot, every fiber owns its
|
|
277
310
|
* own field, scr_switch repoints the active slot — so run()'s window
|
|
278
311
|
* rides the fiber across awaits, and a spawned fiber INHERITS the
|
|
@@ -855,7 +888,7 @@ static void scr_promise_settle_from(ScrPromise *dst, ScrPromise *src) {
|
|
|
855
888
|
}
|
|
856
889
|
scr_promise_settle_wake(dst);
|
|
857
890
|
}
|
|
858
|
-
if (src->state == SCR_PROM_REJECTED) src
|
|
891
|
+
if (src->state == SCR_PROM_REJECTED) scr_prom_observe(src);
|
|
859
892
|
}
|
|
860
893
|
|
|
861
894
|
/* The emitted same-type Promise.race adapter (see raceAdapterFor). */
|
|
@@ -1024,7 +1057,7 @@ ScrStr *scr_promise_payload_str(ScrPromise *p) {
|
|
|
1024
1057
|
return scr_str_retain((ScrStr *)p->payload);
|
|
1025
1058
|
}
|
|
1026
1059
|
/* Thin views for the gated dyn-async TU (scr_async_dyn.c): the payload
|
|
1027
|
-
* KIND, whether a REF payload is a
|
|
1060
|
+
* KIND, whether a REF payload is a dyn value (the dyn adapters), and the
|
|
1028
1061
|
* settled-await primitive (park/hop + rejection re-throw; true =
|
|
1029
1062
|
* fulfilled). */
|
|
1030
1063
|
static bool scr_await_settled(ScrPromise *p); /* defined with the await family */
|
|
@@ -1201,7 +1234,7 @@ void scr_await_hop(void) { scr_await_yield(); }
|
|
|
1201
1234
|
static bool scr_await_settled(ScrPromise *p) {
|
|
1202
1235
|
if (p->state != SCR_PROM_PENDING) scr_await_yield();
|
|
1203
1236
|
while (p->state == SCR_PROM_PENDING) scr_await_park(p);
|
|
1204
|
-
p
|
|
1237
|
+
scr_prom_observe(p);
|
|
1205
1238
|
if (p->state == SCR_PROM_REJECTED) {
|
|
1206
1239
|
switch (p->payload_kind) {
|
|
1207
1240
|
case SCR_EXC_F64: scr_throw_f64(p->f64); break;
|
|
@@ -1439,12 +1472,12 @@ ScrPromise *scr_tp_set_immediate(void) {
|
|
|
1439
1472
|
return p;
|
|
1440
1473
|
}
|
|
1441
1474
|
|
|
1442
|
-
/* ── setImmediate as a first-class
|
|
1475
|
+
/* ── setImmediate as a first-class dyn value ─────────────────────────
|
|
1443
1476
|
* The global passed AS A FUNCTION VALUE into untyped code (the Node-suite
|
|
1444
1477
|
* traceCallback shape: `channel.traceCallback(setImmediate, 0, ctx, null,
|
|
1445
1478
|
* cb)`). The minted dyn callable schedules its own immediate that calls
|
|
1446
1479
|
* args[0] with the remaining arguments (Node's setImmediate(cb, ...args)
|
|
1447
|
-
* contract) and answers undefined (the Immediate handle object has no
|
|
1480
|
+
* contract) and answers undefined (the Immediate handle object has no dyn
|
|
1448
1481
|
* story — clearImmediate over this value is not modeled). A non-function
|
|
1449
1482
|
* first argument throws Node's ERR_INVALID_ARG_TYPE synchronously. */
|
|
1450
1483
|
|
|
@@ -2050,7 +2083,7 @@ void scr_loop_set_island_rejections(bool (*fn)(bool print)) {
|
|
|
2050
2083
|
}
|
|
2051
2084
|
|
|
2052
2085
|
/* ── process.on('unhandledRejection') ─────────────────────────────────
|
|
2053
|
-
*
|
|
2086
|
+
* dyn listeners called per never-observed rejection instead of the
|
|
2054
2087
|
* default report below. Node fires the event at end-of-turn; the
|
|
2055
2088
|
* compiled runtime fires at loop exhaustion, where the ledger is
|
|
2056
2089
|
* decided — the same values, later (SEMANTICS.md; mustCall-style
|
|
@@ -2076,8 +2109,12 @@ bool scr_report_unhandled_rejections(void) {
|
|
|
2076
2109
|
if (scr_urj_deliver_fn != NULL) {
|
|
2077
2110
|
/* Listener dispatch (scr_async_dyn.c installed the hook at
|
|
2078
2111
|
* registration): the event handles it, like Node — per entry,
|
|
2079
|
-
* no report, exit 0. A listener throw is the uncaught crash.
|
|
2112
|
+
* no report, exit 0. A listener throw is the uncaught crash.
|
|
2113
|
+
* reported_unhandled arms the 'rejectionHandled' window: a
|
|
2114
|
+
* handler the listener itself attaches fires the sibling event
|
|
2115
|
+
* (scr_prom_observe). */
|
|
2080
2116
|
p->rejection_observed = true;
|
|
2117
|
+
p->reported_unhandled = true;
|
|
2081
2118
|
if (!scr_urj_deliver_fn(p)) crashed = true;
|
|
2082
2119
|
} else if (!any) {
|
|
2083
2120
|
any = true;
|
|
@@ -2118,6 +2155,14 @@ bool scr_report_unhandled_rejections(void) {
|
|
|
2118
2155
|
bool island = scr_island_rejections_fn(!any);
|
|
2119
2156
|
any = any || island;
|
|
2120
2157
|
}
|
|
2158
|
+
/* An 'unhandledRejection' listener can spawn fibers the exhausted loop
|
|
2159
|
+
* will never run (a .catch attach mints a reaction fiber): they are
|
|
2160
|
+
* abandoned by construction — re-note them so the RC audit's skip
|
|
2161
|
+
* covers their parked state, exactly the loop-teardown accounting. */
|
|
2162
|
+
if (scr_fibers_live > scr_fibers_abandoned) {
|
|
2163
|
+
scr_fibers_abandoned = scr_fibers_live;
|
|
2164
|
+
scr_note_abandoned_fibers(scr_fibers_abandoned);
|
|
2165
|
+
}
|
|
2121
2166
|
/* main returns 1 on a reported rejection — the 'exit' listeners (atexit)
|
|
2122
2167
|
* must see that code, like Node's. */
|
|
2123
2168
|
if (any) scr_exit_code_note(1);
|
package/src/scr_async_dyn.c
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Checked-dynamic ASYNC surfaces (gated — cc.ts links this TU only when
|
|
2
2
|
* the IR carries the crossing libCalls or dyn dispatch: the scr_dc.c
|
|
3
3
|
* size-class precedent). Everything here rides scr_async.c's public
|
|
4
|
-
* machinery: the
|
|
4
|
+
* machinery: the checked-dynamic tree-promise reaction helpers (.then/.catch/.finally
|
|
5
5
|
* over SCR_DYN_PROMISE, await of a checked-dynamic value, the
|
|
6
6
|
* `new Promise(setImmediate)` constructor), the AsyncLocalStorage API
|
|
7
7
|
* over the fiber-carried snapshots (the always-linked core keeps only
|
|
@@ -128,7 +128,7 @@ void scr_als_disable(double id) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/* run(store, fn, ...args) / exit(fn, ...args): enter (or clear), call the
|
|
131
|
-
*
|
|
131
|
+
* dyn function with the forwarded arguments, restore — the finally, so a
|
|
132
132
|
* throw still restores before propagating. Result +1 or NULL pending. */
|
|
133
133
|
static ScrDyn *scr_als_call_in(ScrAlsCtx *prev, ScrDyn *fn, ScrDyn *args) {
|
|
134
134
|
size_t argc = args->kind == SCR_DYN_ARR ? args->v.arr.len : 0;
|
|
@@ -146,59 +146,170 @@ ScrDyn *scr_als_exit_run(double id, ScrDyn *fn, ScrDyn *args) {
|
|
|
146
146
|
return scr_als_call_in(scr_als_enter_absent(id), fn, args);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
/* The two rejection-event registries share one shape: listeners with a
|
|
150
|
+
* `once` flag (auto-removed after one delivery, Node's once) and
|
|
151
|
+
* identity-based removal (the offWarning stance). */
|
|
152
|
+
typedef struct {
|
|
153
|
+
ScrDyn *fn;
|
|
154
|
+
bool once;
|
|
155
|
+
} ScrRejListener;
|
|
156
|
+
|
|
157
|
+
static ScrRejListener *scr_urj_listeners = NULL;
|
|
150
158
|
static size_t scr_nurj = 0, scr_urj_cap = 0;
|
|
159
|
+
static ScrRejListener *scr_rjh_listeners = NULL;
|
|
160
|
+
static size_t scr_nrjh = 0, scr_rjh_cap = 0;
|
|
151
161
|
|
|
152
162
|
static void scr_urj_teardown(void) {
|
|
153
|
-
for (size_t i = 0; i < scr_nurj; i++) scr_dyn_release(scr_urj_listeners[i]);
|
|
163
|
+
for (size_t i = 0; i < scr_nurj; i++) scr_dyn_release(scr_urj_listeners[i].fn);
|
|
154
164
|
free(scr_urj_listeners);
|
|
155
165
|
scr_urj_listeners = NULL;
|
|
156
166
|
scr_nurj = scr_urj_cap = 0;
|
|
157
167
|
}
|
|
158
168
|
|
|
159
|
-
static
|
|
169
|
+
static void scr_rjh_teardown(void) {
|
|
170
|
+
for (size_t i = 0; i < scr_nrjh; i++) scr_dyn_release(scr_rjh_listeners[i].fn);
|
|
171
|
+
free(scr_rjh_listeners);
|
|
172
|
+
scr_rjh_listeners = NULL;
|
|
173
|
+
scr_nrjh = scr_rjh_cap = 0;
|
|
174
|
+
}
|
|
160
175
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
176
|
+
static bool scr_urj_dispatch(ScrPromise *p);
|
|
177
|
+
static void scr_rjh_dispatch(ScrPromise *p);
|
|
178
|
+
|
|
179
|
+
/* Node's ERR_INVALID_ARG_TYPE for a non-function listener; true when the
|
|
180
|
+
* value is fine. */
|
|
181
|
+
static bool scr_rej_check_listener(ScrDyn *fn) {
|
|
182
|
+
if (fn->kind == SCR_DYN_FUNC) return true;
|
|
183
|
+
const char *msg = "The \"listener\" argument must be of type function";
|
|
184
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, strlen(msg), "ERR_INVALID_ARG_TYPE");
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static void scr_rej_push(ScrRejListener **list, size_t *n, size_t *cap, ScrDyn *fn, bool once) {
|
|
189
|
+
if (*n == *cap) {
|
|
190
|
+
*cap = *cap ? *cap * 2 : 4;
|
|
191
|
+
*list = realloc(*list, *cap * sizeof **list);
|
|
192
|
+
if (!*list) scr_ad_oom();
|
|
166
193
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
194
|
+
(*list)[(*n)++] = (ScrRejListener){scr_dyn_retain(fn), once};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void scr_rej_remove(ScrRejListener *list, size_t *n, ScrDyn *fn) {
|
|
198
|
+
for (size_t i = 0; i < *n; i++) {
|
|
199
|
+
ScrDyn *l = list[i].fn;
|
|
200
|
+
bool same = l == fn || (l->kind == SCR_DYN_FUNC && fn->kind == SCR_DYN_FUNC &&
|
|
201
|
+
l->v.fn.clo == fn->v.fn.clo);
|
|
202
|
+
if (same) {
|
|
203
|
+
scr_dyn_release(l);
|
|
204
|
+
memmove(list + i, list + i + 1, (*n - i - 1) * sizeof *list);
|
|
205
|
+
(*n)--;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
171
208
|
}
|
|
172
|
-
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* The hooks arm exactly while their registry is non-empty: Node with
|
|
212
|
+
* every listener removed (off, or once-consumed) reverts to the default
|
|
213
|
+
* report/silence, and the report loop consults the hook per promise. */
|
|
214
|
+
static void scr_urj_sync_hook(void) {
|
|
215
|
+
scr_urj_deliver_fn = scr_nurj > 0 ? scr_urj_dispatch : NULL;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static void scr_rjh_sync_hook(void) {
|
|
219
|
+
scr_rjh_notify_fn = scr_nrjh > 0 ? scr_rjh_dispatch : NULL;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
void scr_process_on_unhandled_rejection(ScrDyn *fn, bool once) {
|
|
223
|
+
if (!scr_rej_check_listener(fn)) return;
|
|
224
|
+
static bool teardown_armed = false;
|
|
225
|
+
if (!teardown_armed) {
|
|
226
|
+
teardown_armed = true;
|
|
173
227
|
atexit(scr_urj_teardown);
|
|
174
|
-
scr_urj_deliver_fn = scr_urj_dispatch; /* arm the loop-end report */
|
|
175
228
|
}
|
|
176
|
-
scr_urj_listeners
|
|
229
|
+
scr_rej_push(&scr_urj_listeners, &scr_nurj, &scr_urj_cap, fn, once);
|
|
230
|
+
scr_urj_sync_hook();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void scr_process_off_unhandled_rejection(ScrDyn *fn) {
|
|
234
|
+
scr_rej_remove(scr_urj_listeners, &scr_nurj, fn);
|
|
235
|
+
scr_urj_sync_hook();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void scr_process_on_rejection_handled(ScrDyn *fn, bool once) {
|
|
239
|
+
if (!scr_rej_check_listener(fn)) return;
|
|
240
|
+
static bool teardown_armed = false;
|
|
241
|
+
if (!teardown_armed) {
|
|
242
|
+
teardown_armed = true;
|
|
243
|
+
atexit(scr_rjh_teardown);
|
|
244
|
+
}
|
|
245
|
+
scr_rej_push(&scr_rjh_listeners, &scr_nrjh, &scr_rjh_cap, fn, once);
|
|
246
|
+
scr_rjh_sync_hook();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void scr_process_off_rejection_handled(ScrDyn *fn) {
|
|
250
|
+
scr_rej_remove(scr_rjh_listeners, &scr_nrjh, fn);
|
|
251
|
+
scr_rjh_sync_hook();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* One registry pass: call every listener with `args`, removing once-
|
|
255
|
+
* listeners BEFORE their call (Node's once removes at dispatch, so a
|
|
256
|
+
* re-registration inside the listener sticks). The registry is accessed
|
|
257
|
+
* THROUGH its pointers per step — a listener that registers can realloc
|
|
258
|
+
* the array mid-pass. Returns false when a listener threw (the caller's
|
|
259
|
+
* crash path). */
|
|
260
|
+
static bool scr_rej_fire(ScrRejListener **list, size_t *n, ScrDyn **args, size_t argc) {
|
|
261
|
+
size_t i = 0;
|
|
262
|
+
bool ok = true;
|
|
263
|
+
while (i < *n && ok) {
|
|
264
|
+
/* Own +1 across the call: a once-removal (here) or the listener
|
|
265
|
+
* removing itself (off inside the body) must not free a running
|
|
266
|
+
* function. */
|
|
267
|
+
ScrDyn *fn = scr_dyn_retain((*list)[i].fn);
|
|
268
|
+
if ((*list)[i].once) {
|
|
269
|
+
scr_dyn_release((*list)[i].fn);
|
|
270
|
+
memmove(*list + i, *list + i + 1, (*n - i - 1) * sizeof **list);
|
|
271
|
+
(*n)--;
|
|
272
|
+
} else {
|
|
273
|
+
i++;
|
|
274
|
+
}
|
|
275
|
+
ScrDyn *r = scr_dyn_call(fn, args, argc, "listener");
|
|
276
|
+
if (r == NULL) ok = false;
|
|
277
|
+
else scr_dyn_release(r);
|
|
278
|
+
scr_dyn_release(fn);
|
|
279
|
+
}
|
|
280
|
+
return ok;
|
|
177
281
|
}
|
|
178
282
|
|
|
179
283
|
/* Dispatch one unhandled rejection to the registered listeners —
|
|
180
284
|
* (reason, promise), Node's signature. A listener throw is an uncaught
|
|
181
|
-
* exception (Node crashes there too):
|
|
182
|
-
*
|
|
285
|
+
* exception (Node crashes there too): the caller prints it and exits 1.
|
|
286
|
+
* A once-consumed-to-empty registry disarms the hook on the way out, so
|
|
287
|
+
* the report's NEXT promise takes the default print — Node's
|
|
288
|
+
* listener-less behavior. */
|
|
183
289
|
static bool scr_urj_dispatch(ScrPromise *p) {
|
|
184
290
|
ScrDyn *reason = scr_promise_reason_dyn(p);
|
|
185
291
|
ScrDyn *boxed = scr_dyn_new_promise(p);
|
|
186
292
|
ScrDyn *args[2] = {reason, boxed};
|
|
187
|
-
bool ok =
|
|
188
|
-
for (size_t i = 0; i < scr_nurj && ok; i++) {
|
|
189
|
-
ScrDyn *r = scr_dyn_call(scr_urj_listeners[i], args, 2, "listener");
|
|
190
|
-
if (r == NULL) ok = false;
|
|
191
|
-
else scr_dyn_release(r);
|
|
192
|
-
}
|
|
293
|
+
bool ok = scr_rej_fire(&scr_urj_listeners, &scr_nurj, args, 2);
|
|
193
294
|
scr_dyn_release(reason);
|
|
194
295
|
scr_dyn_release(boxed);
|
|
296
|
+
scr_urj_sync_hook();
|
|
195
297
|
return ok;
|
|
196
298
|
}
|
|
197
299
|
|
|
300
|
+
/* 'rejectionHandled' delivery — (promise), Node's payload. A listener
|
|
301
|
+
* throw propagates as a pending exception through the attach site. */
|
|
302
|
+
static void scr_rjh_dispatch(ScrPromise *p) {
|
|
303
|
+
ScrDyn *boxed = scr_dyn_new_promise(p);
|
|
304
|
+
(void)scr_rej_fire(&scr_rjh_listeners, &scr_nrjh, &boxed, 1);
|
|
305
|
+
scr_dyn_release(boxed);
|
|
306
|
+
scr_rjh_sync_hook(); /* a once-consumed-to-empty registry disarms */
|
|
307
|
+
}
|
|
308
|
+
|
|
198
309
|
/* `new Promise(setImmediate)` (the Node-suite early-exit shape): the
|
|
199
310
|
* executor IS setImmediate, so resolve rides the immediate queue — a
|
|
200
|
-
* fresh promise an armed immediate fulfills with the undefined
|
|
201
|
-
* (the executor's resolve receives no argument; the
|
|
311
|
+
* fresh promise an armed immediate fulfills with the undefined dyn value
|
|
312
|
+
* (the executor's resolve receives no argument; the dyn payload keeps
|
|
202
313
|
* promise<dyn> awaiters honest and void awaiters ignore it). +1. */
|
|
203
314
|
static void scr_imm_promise_thunk(ScrClosure *self) {
|
|
204
315
|
ScrPromise *p = (ScrPromise *)scr_box_get_ref(self->caps[0]);
|
|
@@ -216,12 +327,12 @@ ScrPromise *scr_immediate_promise(void) {
|
|
|
216
327
|
return p;
|
|
217
328
|
}
|
|
218
329
|
|
|
219
|
-
/* ── .then/.catch/.finally over
|
|
330
|
+
/* ── .then/.catch/.finally over dyn promises (scr_dyn_invoke's promise
|
|
220
331
|
* arm and the dc tracePromise reactions) ──────────────────────────────
|
|
221
332
|
* One reaction fiber per registration: it awaits src (the settled-await
|
|
222
333
|
* microtask hop keeps JS's ordering — reactions never run synchronously
|
|
223
|
-
* inside settle), runs the
|
|
224
|
-
* returning a
|
|
334
|
+
* inside settle), runs the checked-dynamic tree handler, and settles dst. A handler
|
|
335
|
+
* returning a dyn promise is ADOPTED (awaited in a loop, like JS's
|
|
225
336
|
* resolve). Non-callable handlers pass the settlement through (JS's
|
|
226
337
|
* PromisePrototypeThen over non-function reactions). The fiber's own
|
|
227
338
|
* promise is dropped unobserved — the entry consumes every exception
|
|
@@ -258,7 +369,7 @@ static void scr_dyn_then_entry(ScrFiber *self, void *ap) {
|
|
|
258
369
|
scr_promise_fulfill_ref(a->dst, scr_dyn_retain(v), scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
259
370
|
}
|
|
260
371
|
} else {
|
|
261
|
-
/* Adopt
|
|
372
|
+
/* Adopt dyn-promise results (JS's resolve walk). */
|
|
262
373
|
while (r != NULL && r->kind == SCR_DYN_PROMISE) {
|
|
263
374
|
ScrDyn *inner = scr_await_dyn(r->v.promise);
|
|
264
375
|
scr_dyn_release(r);
|
|
@@ -289,6 +400,11 @@ static void scr_dyn_then_entry(ScrFiber *self, void *ap) {
|
|
|
289
400
|
}
|
|
290
401
|
|
|
291
402
|
ScrDyn *scr_dyn_promise_then(ScrPromise *src, ScrDyn *onf, ScrDyn *onr, ScrDyn *onfin) {
|
|
403
|
+
/* A rejection HANDLER marks the source handled at attach (Node's
|
|
404
|
+
* moment; the reaction fiber's await re-marks harmlessly) — this is
|
|
405
|
+
* also what lets a .catch inside an 'unhandledRejection' listener fire
|
|
406
|
+
* 'rejectionHandled' when no fiber turn remains. */
|
|
407
|
+
if (onr != NULL) scr_promise_mark_handled(src);
|
|
292
408
|
ScrDynThenPack *a = malloc(sizeof *a);
|
|
293
409
|
if (!a) scr_ad_oom();
|
|
294
410
|
a->src = scr_promise_retain(src);
|
|
@@ -301,7 +417,7 @@ ScrDyn *scr_dyn_promise_then(ScrPromise *src, ScrDyn *onf, ScrDyn *onr, ScrDyn *
|
|
|
301
417
|
scr_promise_release(waiter); /* the entry never rejects; nobody awaits it */
|
|
302
418
|
return boxed;
|
|
303
419
|
}
|
|
304
|
-
/* `await v` where v is a CHECKED-DYNAMIC value: a
|
|
420
|
+
/* `await v` where v is a CHECKED-DYNAMIC value: a dyn promise adopts
|
|
305
421
|
* (the boxed promise awaits — rejections re-throw); every other kind is
|
|
306
422
|
* JS's await-of-a-non-thenable — one microtask hop, the value itself
|
|
307
423
|
* (+1). Thenable ADOPTION (a plain object carrying a then method) is not
|
|
@@ -314,14 +430,14 @@ ScrDyn *scr_await_dyn_value(ScrDyn *v) {
|
|
|
314
430
|
|
|
315
431
|
/* ── process warnings (emitWarning + the 'warning' event) ─────────────
|
|
316
432
|
* Gated with the rest of this TU (a deprecation-emitting unit's gate
|
|
317
|
-
* must imply the dynAsync link). Listeners are
|
|
433
|
+
* must imply the dynAsync link). Listeners are dyn functions; emission
|
|
318
434
|
* is SYNCHRONOUS at the
|
|
319
435
|
* call (Node defers a tick through nextTick — the MaxListenersExceeded
|
|
320
436
|
* precedent, SEMANTICS.md 138) and the default stderr report always
|
|
321
437
|
* prints (Node's own bootstrap listener; a compiled binary has no
|
|
322
|
-
* --no-warnings). The warning VALUE is the
|
|
438
|
+
* --no-warnings). The warning VALUE is the dyn error encoding built over
|
|
323
439
|
* an ScrError (identity-cached, so a listener comparing two deliveries
|
|
324
|
-
* of one warning sees one object); a string `detail` joins the
|
|
440
|
+
* of one warning sees one object); a string `detail` joins the dyn node
|
|
325
441
|
* and the report's second line, exactly Node. */
|
|
326
442
|
static ScrDyn **scr_warn_listeners = NULL;
|
|
327
443
|
static size_t scr_nwarn = 0, scr_warn_cap = 0;
|
|
@@ -366,7 +482,7 @@ void scr_process_off_warning(ScrDyn *fn) {
|
|
|
366
482
|
}
|
|
367
483
|
}
|
|
368
484
|
|
|
369
|
-
/* Dispatch + the default stderr report over a built warning
|
|
485
|
+
/* Dispatch + the default stderr report over a built warning dyn node.
|
|
370
486
|
* Borrowed. A listener throw propagates (the dc publish stance). */
|
|
371
487
|
static void scr_warning_dispatch(ScrDyn *w) {
|
|
372
488
|
for (size_t i = 0; i < scr_nwarn; i++) {
|
|
@@ -415,7 +531,7 @@ static void scr_warn_bad_arg(const char *arg, const char *want) {
|
|
|
415
531
|
scr_throw_error_msg_code(SCR_ERR_TYPE, buf, (size_t)n, "ERR_INVALID_ARG_TYPE");
|
|
416
532
|
}
|
|
417
533
|
|
|
418
|
-
/* process.emitWarning(...) — Node's full argument grammar over
|
|
534
|
+
/* process.emitWarning(...) — Node's full argument grammar over dyn
|
|
419
535
|
* values: (warning: string | Error), then for string warnings a type
|
|
420
536
|
* string / ctor function / options object ({type, code, detail}) second
|
|
421
537
|
* and a code string / ctor function third. Wrong kinds throw Node's
|
|
@@ -487,11 +603,11 @@ void scr_process_emit_warning(ScrDyn *args) {
|
|
|
487
603
|
|
|
488
604
|
|
|
489
605
|
|
|
490
|
-
/* A caught-exception snapshot as a
|
|
491
|
-
*
|
|
606
|
+
/* A caught-exception snapshot as a dyn value — identity-preserving for
|
|
607
|
+
* dyn payloads (a dyn-thrown value is retained, not copied), the
|
|
492
608
|
* identity-cached %error encoding above for Error-family objects,
|
|
493
609
|
* scalars by value, the type-erased empty object for the rest
|
|
494
|
-
* (SEMANTICS.md 67). Shared by the dc trace choreography, the
|
|
610
|
+
* (SEMANTICS.md 67). Shared by the dc trace choreography, the checked-dynamic tree
|
|
495
611
|
* promise reactions, and the unhandled-rejection dispatch. Borrows the
|
|
496
612
|
* box; result +1. */
|
|
497
613
|
ScrDyn *scr_caught_to_dyn(const ScrCaught *c) {
|
|
@@ -510,8 +626,8 @@ ScrDyn *scr_caught_to_dyn(const ScrCaught *c) {
|
|
|
510
626
|
}
|
|
511
627
|
}
|
|
512
628
|
|
|
513
|
-
/* Await a
|
|
514
|
-
* dyn or void fulfillment): the payload as a
|
|
629
|
+
/* Await a dyn-CROSSING promise (SCR_DYN_PROMISE's boundary contract —
|
|
630
|
+
* dyn or void fulfillment): the payload as a dyn value (+1; a void
|
|
515
631
|
* fulfillment answers the undefined value, and the defensive scalar arms
|
|
516
632
|
* cover payload kinds a direct box could theoretically carry), or NULL
|
|
517
633
|
* with the rejection re-thrown into the awaiter. */
|
|
@@ -528,7 +644,7 @@ ScrDyn *scr_await_dyn(ScrPromise *p) {
|
|
|
528
644
|
}
|
|
529
645
|
case SCR_EXC_REF: {
|
|
530
646
|
void *v = scr_promise_payload_ref(p);
|
|
531
|
-
if (v) return (ScrDyn *)v; /* the
|
|
647
|
+
if (v) return (ScrDyn *)v; /* the dyn contract: a retained dyn */
|
|
532
648
|
return scr_dyn_retain(scr_dyn_undefined());
|
|
533
649
|
}
|
|
534
650
|
default:
|
|
@@ -536,7 +652,7 @@ ScrDyn *scr_await_dyn(ScrPromise *p) {
|
|
|
536
652
|
}
|
|
537
653
|
}
|
|
538
654
|
|
|
539
|
-
/* The rejection reason as a
|
|
655
|
+
/* The rejection reason as a dyn value — the scr_caught_to_dyn stances
|
|
540
656
|
* over a promise's payload slot (identity-preserving for dyn-thrown
|
|
541
657
|
* values and %Error instances). */
|
|
542
658
|
ScrDyn *scr_promise_reason_dyn(const ScrPromise *p) {
|
|
@@ -573,10 +689,10 @@ ScrDyn *scr_promise_reason_dyn(const ScrPromise *p) {
|
|
|
573
689
|
}
|
|
574
690
|
}
|
|
575
691
|
|
|
576
|
-
/* ── promises in the
|
|
692
|
+
/* ── promises in the checked-dynamic tree (SCR_DYN_PROMISE) ────────────────────────────
|
|
577
693
|
* Reference boxes over the fiber machinery's ScrPromise (scr_runtime.h's
|
|
578
694
|
* design note). The boundary contract — a boxed promise settles with a
|
|
579
|
-
*
|
|
695
|
+
* dyn payload — is the CALLERS' to keep: the compiler's converters box
|
|
580
696
|
* promise<dyn> directly and every other inner type through the adapting
|
|
581
697
|
* constructor below. */
|
|
582
698
|
|
|
@@ -588,7 +704,7 @@ ScrDyn *scr_dyn_new_promise(ScrPromise *p) {
|
|
|
588
704
|
|
|
589
705
|
/* The typed-inner box: a fresh destination promise parked on `src`
|
|
590
706
|
* through the Promise.race cb-waiter machinery — `adapt` (emitted,
|
|
591
|
-
* per-inner-type) converts the fulfillment payload into a
|
|
707
|
+
* per-inner-type) converts the fulfillment payload into a dyn value and
|
|
592
708
|
* fulfills the destination; rejections copy raw inside the machinery and
|
|
593
709
|
* count as HANDLED on src (the box is the tracked promise, like a JS
|
|
594
710
|
* .then chain). Already-settled sources adapt inline. Borrows src; +1. */
|
package/src/scr_bytes_io.c
CHANGED
|
@@ -61,7 +61,7 @@ ScrBytes *scr_fs_read_file_bytes(ScrStr *path) {
|
|
|
61
61
|
* utf8 answers a string, Node's other real encodings meet the loud
|
|
62
62
|
* not-supported ladder, unknown names throw ERR_UNKNOWN_ENCODING, and an
|
|
63
63
|
* options object dispatches on its `encoding` member (Node's form). +1
|
|
64
|
-
*
|
|
64
|
+
* dyn value, or NULL with the exception pending. */
|
|
65
65
|
ScrDyn *scr_fs_read_file_sync_dyn(ScrStr *path, const ScrDyn *enc) {
|
|
66
66
|
if (enc->kind == SCR_DYN_OBJ) {
|
|
67
67
|
ScrDyn *ev = scr_dyn_obj_get((ScrDyn *)enc, "encoding", 8); /* borrowed */
|
|
@@ -166,7 +166,7 @@ bool scr_process_stderr_write_bytes(const ScrBytes *b) {
|
|
|
166
166
|
|
|
167
167
|
/* ── the checked-dynamic Buffer compare/equals validators ──────────────
|
|
168
168
|
* Node's argument ladders for buf.equals / buf.compare / Buffer.compare
|
|
169
|
-
* over
|
|
169
|
+
* over dyn-boxed arguments (the invalid-input probes: string needles,
|
|
170
170
|
* '0' offsets, null/object range args). A well-typed dyn still computes
|
|
171
171
|
* the real answer — validation, not a constant fence. */
|
|
172
172
|
|
|
@@ -260,7 +260,7 @@ double scr_fs_to_unix_timestamp(const ScrDyn *t) {
|
|
|
260
260
|
|
|
261
261
|
/* ── the fs argument-validation ladders (checked-dynamic lane) ─────────
|
|
262
262
|
* Each fs.*Chk libCall replicates its API's Node-order validation over
|
|
263
|
-
*
|
|
263
|
+
* dyn values and throws Node's exact typed errors; when every validation
|
|
264
264
|
* passes, the honest tail runs — the real operation where one exists
|
|
265
265
|
* (mkdtempSync, lchmodSync on macOS), the compiler-rendered SC2020 fence
|
|
266
266
|
* otherwise (scr_throw_lowering_fence). All arguments borrowed. */
|
|
@@ -282,7 +282,7 @@ static bool scr_fs_cb_chk(const ScrDyn *cb, const char *name) {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/* getValidatedPath: strings and Buffers pass (URL instances never reach
|
|
285
|
-
* these ladders — the
|
|
285
|
+
* these ladders — the checked-dynamic tree has no URL kind here, and Node would accept
|
|
286
286
|
* only file: URLs anyway). */
|
|
287
287
|
static bool scr_fs_path_chk(const ScrDyn *p, const char *name) {
|
|
288
288
|
if (p->kind == SCR_DYN_STR || p->kind == SCR_DYN_BYTES) return true;
|
|
@@ -522,7 +522,7 @@ static void scr_fs_lchmod_apply(const ScrDyn *path, const ScrDyn *mode) {
|
|
|
522
522
|
}
|
|
523
523
|
#endif
|
|
524
524
|
|
|
525
|
-
/* Answers the
|
|
525
|
+
/* Answers the dyn undefined (+1) on success — lchmodSync's JS value, so
|
|
526
526
|
* return-position uses lower; NULL with the exception pending. */
|
|
527
527
|
ScrDyn *scr_fs_lchmod_sync_chk(const ScrDyn *path, const ScrDyn *mode) {
|
|
528
528
|
if (!scr_fs_lchmod_defined("fs.lchmodSync")) return NULL;
|