@scriptc/runtime 0.0.12 → 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_async.c +48 -3
- package/src/scr_async_dyn.c +139 -23
- package/src/scr_dyn_invoke.c +27 -3
- package/src/scr_http.c +1076 -30
- package/src/scr_http2.c +50 -0
- package/src/scr_json.c +21 -0
- package/src/scr_net.c +253 -34
- package/src/scr_runtime.h +135 -7
- package/src/scr_tls.c +40 -3
- package/src/scr_tls_ca.c +288 -0
package/package.json
CHANGED
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++;
|
|
@@ -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). */
|
|
@@ -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;
|
|
@@ -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
|
@@ -146,55 +146,166 @@ 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
311
|
* fresh promise an armed immediate fulfills with the undefined dyn value
|
|
@@ -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);
|
package/src/scr_dyn_invoke.c
CHANGED
|
@@ -671,9 +671,33 @@ ScrDyn *scr_dyn_invoke(ScrDyn *recv, const char *method, ScrDyn *const *args, si
|
|
|
671
671
|
return NULL;
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
-
if (recv->kind == SCR_DYN_BYTES
|
|
675
|
-
|
|
676
|
-
|
|
674
|
+
if (recv->kind == SCR_DYN_BYTES) {
|
|
675
|
+
ScrBytes *bytes = recv->v.bytes;
|
|
676
|
+
size_t blen = bytes->len;
|
|
677
|
+
if (dyn_name_is(method, "at")) {
|
|
678
|
+
double iD = dyn_index_arg(args, argc, 0, 0, what);
|
|
679
|
+
if (scr_exc_pending()) return NULL;
|
|
680
|
+
double idx = iD < 0 ? (double)blen + iD : iD;
|
|
681
|
+
if (idx < 0 || idx >= (double)blen) return scr_dyn_retain(scr_dyn_undefined());
|
|
682
|
+
return scr_dyn_new_num((double)bytes->data[(size_t)idx]);
|
|
683
|
+
}
|
|
684
|
+
if (dyn_name_is(method, "slice") || dyn_name_is(method, "subarray")) {
|
|
685
|
+
/* Both COPY (no views in this runtime — the static lane's
|
|
686
|
+
* documented divergence for subarray/Buffer.slice); the result
|
|
687
|
+
* keeps the receiver's Buffer flavor. */
|
|
688
|
+
double startD = dyn_index_arg(args, argc, 0, 0, what);
|
|
689
|
+
if (scr_exc_pending()) return NULL;
|
|
690
|
+
double endD = dyn_index_arg(args, argc, 1, (double)blen, what);
|
|
691
|
+
if (scr_exc_pending()) return NULL;
|
|
692
|
+
ScrBytes *out = scr_bytes_slice(bytes, startD, endD);
|
|
693
|
+
ScrDyn *d = recv->buffer ? scr_dyn_new_buffer_copy(out) : scr_dyn_new_bytes_copy(out);
|
|
694
|
+
scr_bytes_release(out);
|
|
695
|
+
return d;
|
|
696
|
+
}
|
|
697
|
+
if (dyn_bytes_proto_real(method)) {
|
|
698
|
+
dyn_throw_unsupported("Uint8Array", method);
|
|
699
|
+
return NULL;
|
|
700
|
+
}
|
|
677
701
|
}
|
|
678
702
|
|
|
679
703
|
/* NUM/BOOL/BYTES-remainder: the name is no method of this kind — JS's
|