Merge main

This commit is contained in:
Chris Tate
2026-10-17 02:35:31 -05:00
63 changed files with 1111 additions and 306 deletions
+4 -4
View File
@@ -2265,14 +2265,14 @@ static void scr_exec_file_fire(ScrChild *c) {
}
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb,
ScrExecFileFn fn, bool shell) {
ScrExecFileFn fn) {
ScrStr *empty = scr_str_new("", 0);
ScrChild *c = scr_spawn_opts(cmd, args, 3, 3, 3, 0, 0, false, false, false,
NULL, empty);
scr_str_release(empty);
c->exec_cb = cb;
c->exec_fn = fn;
char *display = scr_exec_display(cmd, args, shell);
char *display = scr_exec_display(cmd, args, false);
c->exec_display = scr_str_new(display, strlen(display));
free(display);
scr_child_stream_capture_begin(c->out_stream);
@@ -4905,14 +4905,14 @@ static void scr_exec_file_fire(ScrChild *c) {
}
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb,
ScrExecFileFn fn, bool shell) {
ScrExecFileFn fn) {
ScrStr *empty = scr_str_new("", 0);
ScrChild *c = scr_spawn_opts(cmd, args, 3, 3, 3, 0, 0, false, false, false,
NULL, empty);
scr_str_release(empty);
c->exec_cb = cb;
c->exec_fn = fn;
char *display = scr_exec_display(cmd, args, shell);
char *display = scr_exec_display(cmd, args, false);
c->exec_display = scr_str_new(display, strlen(display));
free(display);
scr_child_stream_capture_begin(c->out_stream);
+2 -4
View File
@@ -155,11 +155,9 @@ ScrChild *scr_spawn_dynamic(ScrStr *cmd, ScrArr *args, const ScrDyn *options);
void scr_child_on_spawn(ScrChild *child, ScrClosure *cb); /* moves zero-argument callback */
/* The callback slice: file + args, default options. The returned child has
* all three stdio slots piped; stdout/stderr are captured internally and
* the callback moves into the child registry until settlement. With shell
* (exec), args is ["-c", command] for /bin/sh and failure messages show
* the command, as for scr_exec_sync. */
* the callback moves into the child registry until settlement. */
ScrChild *scr_exec_file(ScrStr *cmd, ScrArr *args, ScrClosure *cb /*moves*/,
ScrExecFileFn fn, bool shell);
ScrExecFileFn fn);
ScrChild *scr_child_retain(ScrChild *c);
void scr_child_release(ScrChild *c);
void *scr_child_retain_v(void *p);
+12
View File
@@ -603,6 +603,18 @@ ScrDyn *scr_dyn_new_symbol(ScrSym *value); /* borrows and retains value */
ScrSym *scr_sym_well_known(ScrStr *name);
ScrDyn *scr_dyn_symbol_get(const ScrDyn *value, const ScrStr *key);
ScrDyn *scr_dyn_symbol_key_get(const ScrDyn *value, const ScrDyn *key, bool optional);
/* `using` and `await using` with a dyn value. scr_dyn_using_method runs the
* declaration's checks: null and undefined answer undefined (nothing to
* dispose); any other value that is not an object or function throws
* Node's TypeError, as does a missing or non-callable [Symbol.dispose].
* With `async`, [Symbol.asyncDispose] is read first and [Symbol.dispose]
* is the fallback, and the answer records which one was found.
* scr_dyn_using_dispose calls that answer with `value` as `this` at scope
* exit and returns the call's result (+1; undefined for a sync fallback,
* whose result `await using` does not await). Both borrow their operands
* and return NULL with the exception pending. */
ScrDyn *scr_dyn_using_method(const ScrDyn *value, bool async);
ScrDyn *scr_dyn_using_dispose(const ScrDyn *method, const ScrDyn *value);
void scr_dyn_symbol_key_set(ScrDyn *value, ScrDyn *key, ScrDyn *stored);
void scr_dyn_symbol_key_placeholder(ScrDyn *value, ScrDyn *key);
bool scr_dyn_has_key_computed(const ScrDyn *value, const ScrDyn *key);
+29 -3
View File
@@ -33,7 +33,8 @@ static const char *const scr_error_names[SCR_ERR_COUNT] = {
/* DOMException's DEFAULT name is "Error" too (WebIDL: the name
* parameter defaults to "Error"); the class identity lives in the
* vtable, never the name. */
"Error", "ReferenceError", "EvalError", "URIError", "AggregateError", "AssertionError"};
"Error", "ReferenceError", "EvalError", "URIError", "AggregateError", "AssertionError",
"SuppressedError"};
void scr_error_trace(void *e, ScrTraceVisit visit, void *ctx) {
/* name/message are strings — never collector-headered; nothing to visit. */
@@ -140,6 +141,28 @@ static void scr_aggregate_destroy(void *obj) {
scr_error_destroy(obj);
}
/* SuppressedError teardown: the ScrError fields plus the disposal
* snapshots scr_exc_suppress recorded for its `error` and `suppressed`. */
static void scr_suppressed_gcfree(void *obj) {
scr_exc_suppressed_forget(obj);
scr_error_gcfree(obj);
}
static void scr_suppressed_destroy(void *obj) {
scr_exc_suppressed_forget(obj);
scr_error_destroy(obj);
}
static void scr_suppressed_reld(void *obj) {
ScrError *e = (ScrError *)obj;
if (--e->rc == 0) {
if (scr_error_traced) scr_cyc_dead(e);
scr_rc_destroy(obj, scr_suppressed_destroy);
} else if (scr_error_traced) {
scr_cyc_released(e);
}
}
static void scr_aggregate_reld(void *obj) {
ScrAggregateError *a = (ScrAggregateError *)obj;
if (--a->base.rc == 0) {
@@ -163,6 +186,7 @@ SCR_TL ScrVt scr_error_vts[SCR_ERR_COUNT] = {
{7, 7, &scr_error_reld}, /* URIError */
{8, 8, &scr_aggregate_reld}, /* AggregateError */
{9, 9, &scr_error_reld}, /* AssertionError */
{10, 10, &scr_suppressed_reld}, /* SuppressedError */
};
ScrError *scr_error_retain(ScrError *e) {
@@ -208,11 +232,13 @@ static ScrError *scr_error_alloc(int kind) {
static ScrCycType error_type = { &scr_error_trace, &scr_error_gcfree, SCR_CYC_KIND_ERROR };
static ScrCycType domex_type = { &scr_error_trace, &scr_domex_gcfree, SCR_CYC_KIND_DOMEXCEPTION };
static ScrCycType aggregate_type = { &scr_error_trace, &scr_aggregate_gcfree, SCR_CYC_KIND_ERROR };
static ScrCycType suppressed_type = { &scr_error_trace, &scr_suppressed_gcfree, SCR_CYC_KIND_ERROR };
ScrError *e;
if (scr_error_traced) {
e = scr_cyc_alloc(size, kind == SCR_ERR_DOMEX ? &domex_type
: kind == SCR_ERR_AGGREGATE ? &aggregate_type
: &error_type);
: kind == SCR_ERR_AGGREGATE ? &aggregate_type
: kind == SCR_ERR_SUPPRESSED ? &suppressed_type
: &error_type);
} else {
e = calloc(1, size);
if (!e) scr_error_oom();
+10 -1
View File
@@ -119,7 +119,8 @@ enum {
SCR_ERR_URI = 7,
SCR_ERR_AGGREGATE = 8, /* AggregateError — ScrAggregateError, the wider layout */
SCR_ERR_ASSERTION = 9, /* node:assert's AssertionError — the Error layout */
SCR_ERR_COUNT = 10,
SCR_ERR_SUPPRESSED = 10, /* SuppressedError — the Error layout; error and suppressed are view properties */
SCR_ERR_COUNT = 11,
};
extern SCR_TL ScrVt scr_error_vts[SCR_ERR_COUNT]; /* indexed by SCR_ERR_*; main() stamps pre/post */
@@ -201,6 +202,14 @@ ScrError *scr_aggregate_new(const struct ScrDyn *message, const struct ScrDyn *o
void scr_aggregate_set_errors(ScrError *e, struct ScrDyn *errors); /* both borrowed */
struct ScrDyn *scr_aggregate_errors(ScrError *e); /* +1 */
/* new SuppressedError(error, suppressed, message) (scr_json.c): the message
* as in scr_error_new_options, then `error` and `suppressed` as
* non-enumerable properties of the error's dyn view. All arguments are
* borrowed (dyn undefined when absent). Returns +1, or NULL with an
* exception pending when the message conversion throws. */
ScrError *scr_suppressed_new(const struct ScrDyn *error, const struct ScrDyn *suppressed,
const struct ScrDyn *message);
/* Switch runtime-side error allocation to collector-headered (called from
* main() when the compiler's cycle fixpoint marks the hierarchy). */
void scr_error_set_traced(void);
+51 -9
View File
@@ -354,10 +354,41 @@ void scr_rethrow(const ScrCaught *c) {
}
}
/* Explicit resource management's error merge. The full JavaScript object
* exposes `.error` and `.suppressed`; the static catch surface currently
* exposes Error name/message, so retain Node's externally visible identity
* and ordering while consuming both snapshots exactly once. */
/* Explicit resource management's error merge: a SuppressedError whose
* `error` is the disposal error and whose `suppressed` is the body error.
* The snapshots wait in this table for the error's dyn view, so programs
* that never read them link no checked-dynamic code. */
typedef struct ScrSuppressedEntry {
const void *error_object;
ScrCaught *error;
ScrCaught *suppressed;
} ScrSuppressedEntry;
static SCR_TL ScrSuppressedEntry *scr_suppressed_entries = NULL;
static SCR_TL size_t scr_suppressed_len = 0;
static SCR_TL size_t scr_suppressed_cap = 0;
bool scr_exc_suppressed_values(const void *error_object, const ScrCaught **error,
const ScrCaught **suppressed) {
for (size_t i = 0; i < scr_suppressed_len; i++) {
if (scr_suppressed_entries[i].error_object != error_object) continue;
*error = scr_suppressed_entries[i].error;
*suppressed = scr_suppressed_entries[i].suppressed;
return true;
}
return false;
}
void scr_exc_suppressed_forget(const void *error_object) {
for (size_t i = 0; i < scr_suppressed_len; i++) {
if (scr_suppressed_entries[i].error_object != error_object) continue;
ScrSuppressedEntry entry = scr_suppressed_entries[i];
scr_suppressed_entries[i] = scr_suppressed_entries[--scr_suppressed_len];
scr_caught_release(entry.error);
scr_caught_release(entry.suppressed);
return;
}
}
void scr_exc_suppress(ScrCaught *suppressed) {
if (suppressed->kind == SCR_EXC_GENRET) {
/* A generator .return() is a return completion, not a thrown error.
@@ -371,11 +402,22 @@ void scr_exc_suppress(ScrCaught *suppressed) {
scr_caught_release(suppressed);
return;
}
scr_caught_release(error);
scr_caught_release(suppressed);
scr_throw_error_named(
scr_str_new("SuppressedError", sizeof("SuppressedError") - 1),
scr_str_new("An error was suppressed during disposal", sizeof("An error was suppressed during disposal") - 1));
static const char text[] = "An error was suppressed during disposal";
ScrStr *message = scr_str_new(text, sizeof text - 1);
ScrError *e = scr_error_new(SCR_ERR_SUPPRESSED, message);
scr_str_release(message);
if (scr_suppressed_len == scr_suppressed_cap) {
size_t cap = scr_suppressed_cap ? scr_suppressed_cap * 2 : 4;
ScrSuppressedEntry *entries = realloc(scr_suppressed_entries, cap * sizeof *entries);
if (!entries) {
fputs("scriptc: out of memory\n", stderr);
abort();
}
scr_suppressed_entries = entries;
scr_suppressed_cap = cap;
}
scr_suppressed_entries[scr_suppressed_len++] = (ScrSuppressedEntry){e, error, suppressed};
scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg());
}
bool scr_caught_instanceof(const ScrCaught *c, size_t pre, size_t post) {
+9 -1
View File
@@ -165,8 +165,16 @@ ScrCaught *scr_caught_retain(ScrCaught *c);
void scr_caught_release(ScrCaught *c); /* NULL-tolerant */
void scr_caught_trace_v(void *c, ScrTraceVisit visit, void *ctx);
/* Replace the pending disposal error with a SuppressedError and consume the
* original body-error snapshot. The current exception cell must be pending. */
* original body-error snapshot. The current exception cell must be pending.
* The SuppressedError keeps both snapshots in a side table until its
* teardown: scr_exc_suppressed_values answers them (borrowed) for the
* error's `error` (the disposal error) and `suppressed` (the body error)
* when scr_json.c builds its dyn view, and scr_exc_suppressed_forget
* releases them. */
void scr_exc_suppress(ScrCaught *suppressed);
bool scr_exc_suppressed_values(const void *error_object, const ScrCaught **error,
const ScrCaught **suppressed);
void scr_exc_suppressed_forget(const void *error_object);
/* Borrows the box; re-raises the saved exception exactly (payload RETAINED
* — the binding stays live until its scope exits). */
void scr_rethrow(const ScrCaught *c);
+28
View File
@@ -5677,6 +5677,16 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) {
scr_dyn_obj_set(d, "cause", 5, scr_domex_cause((ScrError *)e));
}
}
/* A disposal SuppressedError's non-enumerable `error` and `suppressed`
* (scr_exc_suppress); a constructed one sets them on its view. */
const ScrCaught *suppressed_error = NULL, *suppressed_value = NULL;
if (e->vt == &scr_error_vts[SCR_ERR_SUPPRESSED] &&
scr_exc_suppressed_values(e, &suppressed_error, &suppressed_value)) {
scr_dyn_obj_set(d, "error", 5, scr_caught_to_dyn(suppressed_error));
d->v.obj.entries[d->v.obj.len - 1].enumerable = false;
scr_dyn_obj_set(d, "suppressed", 10, scr_caught_to_dyn(suppressed_value));
d->v.obj.entries[d->v.obj.len - 1].enumerable = false;
}
/* AggregateError's own non-enumerable `errors` list, shared with the
* typed read so both spellings answer the same array. */
if (e->vt == &scr_error_vts[SCR_ERR_AGGREGATE]) {
@@ -10888,6 +10898,24 @@ ScrError *scr_error_new_options(int kind, const ScrDyn *message, const ScrDyn *o
return e;
}
/* SuppressedError's `error` and `suppressed`: non-enumerable own
* properties of the error's dyn view. Both values move in. */
static void scr_suppressed_attach(ScrError *e, ScrDyn *error, ScrDyn *suppressed) {
ScrDyn *view = scr_dyn_from_error(e);
scr_dyn_obj_set(view, "error", 5, error);
view->v.obj.entries[view->v.obj.len - 1].enumerable = false;
scr_dyn_obj_set(view, "suppressed", 10, suppressed);
view->v.obj.entries[view->v.obj.len - 1].enumerable = false;
scr_dyn_release(view);
}
ScrError *scr_suppressed_new(const ScrDyn *error, const ScrDyn *suppressed, const ScrDyn *message) {
ScrError *e = scr_error_new_options(SCR_ERR_SUPPRESSED, message, NULL);
if (!e) return NULL;
scr_suppressed_attach(e, scr_dyn_retain((ScrDyn *)error), scr_dyn_retain((ScrDyn *)suppressed));
return e;
}
ScrDyn *scr_error_cause(ScrError *e) {
size_t hit = scr_errdyn_find(e, true);
if (hit != SIZE_MAX) {
+101
View File
@@ -356,3 +356,104 @@ void scr_dyn_dirent_finish(ScrDyn *obj) {
break;
}
}
/* The well-known symbol `Symbol.<name>` as a dyn key (+1). */
static ScrDyn *scr_using_key(const char *name, size_t len) {
ScrStr *text = scr_str_new(name, len);
ScrSym *symbol = scr_sym_well_known(text);
scr_str_release(text);
ScrDyn *key = scr_dyn_new_symbol(symbol);
scr_sym_release(symbol);
return key;
}
static bool scr_using_typeof_is(const ScrDyn *value, const char *name, size_t len) {
ScrStr *type = scr_dyn_typeof(value);
bool match = type->len == len && !memcmp(type->data, name, len);
scr_str_release(type);
return match;
}
/* The [Symbol.<name>] method of a `using` value: NULL for an absent member
* (undefined or null), the callable member (+1), or NULL with a pending
* TypeError when the member is not callable. */
static ScrDyn *scr_using_get_method(const ScrDyn *value, const char *name, size_t len, bool *absent) {
ScrDyn *key = scr_using_key(name, len);
ScrDyn *method = scr_dyn_symbol_key_get(value, key, false);
scr_dyn_release(key);
*absent = false;
if (!method) return NULL;
if (method->kind == SCR_DYN_UNDEF || method->kind == SCR_DYN_NULL) {
scr_dyn_release(method);
*absent = true;
return NULL;
}
if (!scr_using_typeof_is(method, "function", 8)) {
scr_dyn_release(method);
ScrJsonBuf message;
scr_jb_init(&message);
scr_jb_puts(&message, "Symbol(Symbol.");
scr_jb_write(&message, name, len);
scr_jb_puts(&message, ") is not a function");
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&message));
return NULL;
}
return method;
}
ScrDyn *scr_dyn_using_method(const ScrDyn *value, bool async) {
if (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF) return scr_dyn_retain(scr_dyn_undefined());
if (!scr_using_typeof_is(value, "object", 6) && !scr_using_typeof_is(value, "function", 8)) {
static const char message[] = "An object is expected with `using` declarations";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
return NULL;
}
bool absent = false;
if (async) {
ScrDyn *method = scr_using_get_method(value, "asyncDispose", 12, &absent);
if (!method && !absent) return NULL;
if (method) {
ScrDyn *record = scr_dyn_new_arr();
scr_dyn_arr_push(record, method);
scr_dyn_arr_push(record, scr_dyn_new_bool(false));
return record;
}
}
ScrDyn *method = scr_using_get_method(value, "dispose", 7, &absent);
if (!method) {
if (absent && value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 &&
!memcmp(value->v.typed_ref.type_key, "object:", 7)) {
/* A class instance seen through an `unknown` or Disposable-typed
* value: its dyn view carries no methods. */
static const char message[] =
"disposing a class instance through a Disposable- or unknown-typed value has no lowering "
"yet (declare the variable with the class type)";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
} else if (absent) {
static const char message[] = "Symbol(Symbol.dispose) is not a function";
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
}
return NULL;
}
if (!async) return method;
ScrDyn *record = scr_dyn_new_arr();
scr_dyn_arr_push(record, method);
scr_dyn_arr_push(record, scr_dyn_new_bool(true));
return record;
}
ScrDyn *scr_dyn_using_dispose(const ScrDyn *method, const ScrDyn *value) {
if (method->kind == SCR_DYN_UNDEF) return scr_dyn_retain(scr_dyn_undefined());
bool sync_fallback = false;
const ScrDyn *callee = method;
if (method->kind == SCR_DYN_ARR) {
callee = method->v.arr.items[0];
sync_fallback = method->v.arr.items[1]->v.b;
}
scr_dyn_this_push_dyn(value);
ScrDyn *result = scr_dyn_call(callee, NULL, 0, "[Symbol.dispose]");
scr_dyn_this_pop();
if (!result || !sync_fallback) return result;
scr_dyn_release(result);
return scr_dyn_retain(scr_dyn_undefined());
}