Merge main into EventTarget

This commit is contained in:
Chris Tate
2026-10-14 08:46:13 -05:00
55 changed files with 2409 additions and 178 deletions
+124 -18
View File
@@ -596,11 +596,11 @@ ScrDyn *scr_await_dyn_value_settled(ScrDyn *value) {
/* ── process warnings (emitWarning + the 'warning' event) ─────────────
* Gated with the rest of this TU (a deprecation-emitting unit's gate
* must imply the dynAsync link). Listeners are dyn functions; emission
* is SYNCHRONOUS at the
* call (Node defers a tick through nextTick — the MaxListenersExceeded
* precedent, SEMANTICS.md 138) and the default stderr report always
* prints (Node's own bootstrap listener; a compiled binary has no
* must imply the dynAsync link). Listeners are dyn functions; the
* warning is built at the call and emitted on the next tick, as Node's
* emitWarning does (scr_warning_emit_deferred). The default stderr report
* prints for every warning except deprecations under process.noDeprecation
* (Node's own bootstrap listener; a compiled binary has no
* --no-warnings). The warning VALUE is the dyn error encoding built over
* an ScrError (identity-cached, so a listener comparing two deliveries
* of one warning sees one object); a string `detail` joins the dyn node
@@ -649,14 +649,71 @@ void scr_process_off_warning(ScrDyn *fn) {
}
}
/* process.noDeprecation (0) and process.throwDeprecation (1): ordinary
* writable process properties, undefined until the program assigns them.
* A compiled binary takes no Node command-line flags, so only those
* assignments set them. */
static SCR_TL ScrDyn *scr_deprecation_flags[2] = {NULL, NULL};
static SCR_TL bool scr_deprecation_flags_registered = false;
static void scr_deprecation_flags_teardown(void) {
for (size_t i = 0; i < 2; i++) {
scr_dyn_release(scr_deprecation_flags[i]);
scr_deprecation_flags[i] = NULL;
}
}
ScrDyn *scr_process_deprecation_flag(double which) {
ScrDyn *value = scr_deprecation_flags[which != 0];
return scr_dyn_retain(value ? value : scr_dyn_undefined());
}
void scr_process_set_deprecation_flag(double which, ScrDyn *value) {
if (!scr_deprecation_flags_registered) {
scr_deprecation_flags_registered = true;
scr_atexit(scr_deprecation_flags_teardown);
}
size_t slot = which != 0;
ScrDyn *previous = scr_deprecation_flags[slot];
scr_deprecation_flags[slot] = scr_dyn_retain(value);
scr_dyn_release(previous);
}
bool scr_process_deprecation_flag_on(int which) {
const ScrDyn *value = scr_deprecation_flags[which != 0];
return value != NULL && scr_dyn_truthy(value);
}
/* The warning's name as warning.toString() reads it: the own property, or
* the Error class name an emitted Error instance inherits. +1 or NULL. */
static ScrStr *scr_warning_name(const ScrDyn *w) {
const ScrDyn *en = scr_dyn_obj_get(w, "name", 4);
if (en) return en->kind == SCR_DYN_STR ? scr_str_retain(en->v.str) : NULL;
ScrError *e = scr_errdyn_err_of(w);
if (!e) return NULL;
ScrStr *name = e->name ? scr_str_retain(e->name) : scr_error_default_name(e);
scr_error_release(e);
return name;
}
static bool scr_warning_is_deprecation(const ScrDyn *w) {
ScrStr *name = scr_warning_name(w);
bool deprecation = name && name->len == 18 && memcmp(name->data, "DeprecationWarning", 18) == 0;
scr_str_release(name);
return deprecation;
}
/* The default stderr report + dispatch over a built warning dyn node.
* Borrowed. A listener throw propagates (the dc publish stance). */
static void scr_warning_dispatch(ScrDyn *w) {
/* "(node:pid) [CODE] Name: message" + "\n<detail>" — Node's
* onWarning report, written as ONE console.error line (so the global
* console's group indentation applies to each of its lines). Node
* registers that listener at startup, so it runs before the program's. */
const ScrDyn *en = scr_dyn_obj_get(w, "name", 4);
* registers that listener at startup, so it runs before the program's,
* and skips deprecations while process.noDeprecation is set. */
bool deprecation = scr_warning_is_deprecation(w);
if (deprecation && scr_process_deprecation_flag_on(0)) goto listeners;
ScrStr *name = scr_warning_name(w);
const ScrDyn *em = scr_dyn_obj_get(w, "message", 7);
const ScrDyn *ec = scr_dyn_obj_get(w, "code", 4);
const ScrDyn *ed = scr_dyn_obj_get(w, "detail", 6);
@@ -670,24 +727,29 @@ static void scr_warning_dispatch(ScrDyn *w) {
scr_jb_put_str(&report, ec->v.str);
scr_jb_puts(&report, "] ");
}
if (en && en->kind == SCR_DYN_STR) scr_jb_put_str(&report, en->v.str);
if (name) scr_jb_put_str(&report, name);
else scr_jb_puts(&report, "Warning");
scr_str_release(name);
scr_jb_puts(&report, ": ");
if (em && em->kind == SCR_DYN_STR) scr_jb_put_str(&report, em->v.str);
if (ed && ed->kind == SCR_DYN_STR) {
scr_jb_puts(&report, "\n");
scr_jb_put_str(&report, ed->v.str);
}
/* Node's one-time trace hint, after the first report. */
/* Node's one-time trace hint, after the first report; deprecations
* name their own trace flag. */
static SCR_TL bool hinted = false;
if (!hinted) {
hinted = true;
scr_jb_puts(&report, "\n(Use `node --trace-warnings ...` to show where the warning was created)");
scr_jb_puts(&report, deprecation
? "\n(Use `node --trace-deprecation ...` to show where the warning was created)"
: "\n(Use `node --trace-warnings ...` to show where the warning was created)");
}
ScrLogArg line = {.tag = SCR_ARG_STR};
line.v.s = scr_jb_finish(&report);
scr_console_error(1, &line);
scr_str_release(line.v.s);
listeners:
for (size_t i = 0; i < scr_nwarn; i++) {
ScrDyn *r = scr_dyn_call(scr_warn_listeners[i], &w, 1, "listener");
if (r == NULL) return; /* pending exception propagates */
@@ -695,20 +757,63 @@ static void scr_warning_dispatch(ScrDyn *w) {
}
}
/* A warning built from C parts (the runtime deprecation sites): name
* defaults to "Warning"; code/detail optional. Borrows message. */
void scr_emit_warning(const char *name, const char *code, ScrStr *message) {
/* A string warning with a type and an optional code, as
* process.emitWarning(message, type, code) builds it. Borrows; +1. */
ScrDyn *scr_warning_new(ScrStr *message, const char *type, const ScrStr *code) {
ScrError *e = scr_error_new(SCR_ERR_ERROR, message);
scr_str_release(e->name);
e->name = scr_str_new(name ? name : "Warning", strlen(name ? name : "Warning"));
e->name = scr_str_new(type, strlen(type));
e->name_present = true;
if (code) e->code = scr_str_new(code, strlen(code));
if (code) e->code = scr_str_retain((ScrStr *)code);
ScrDyn *w = scr_dyn_from_error(e);
scr_error_release(e);
return w;
}
/* A warning built from C parts (the runtime deprecation sites): name
* defaults to "Warning"; code/detail optional. Reported synchronously.
* Borrows message. */
void scr_emit_warning(const char *name, const char *code, ScrStr *message) {
ScrStr *code_text = code ? scr_str_new(code, strlen(code)) : NULL;
ScrDyn *w = scr_warning_new(message, name ? name : "Warning", code_text);
scr_str_release(code_text);
scr_warning_dispatch(w);
scr_dyn_release(w);
}
/* Node's emitWarning tail for runtime-built warnings: deprecations are
* dropped while process.noDeprecation is set and thrown from their own
* tick while process.throwDeprecation is set; every other warning reaches
* the report and the 'warning' listeners on the next tick. */
static void scr_warning_tick(ScrClosure *c) {
ScrDyn *w = scr_box_get_ref(c->caps[0]);
scr_warning_dispatch(w);
scr_dyn_release(w);
}
static void scr_warning_throw_tick(ScrClosure *c) {
ScrDyn *w = scr_box_get_ref(c->caps[0]);
scr_throw_ref(w, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
}
static void scr_warning_defer(ScrDyn *w, void (*fire)(ScrClosure *)) {
ScrClosure *c = scr_closure_new((void *)fire, 1);
c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
scr_box_set_ref(c->caps[0], scr_dyn_retain(w));
scr_next_tick(c);
}
void scr_warning_emit_deferred(ScrDyn *w) {
if (scr_warning_is_deprecation(w)) {
if (scr_process_deprecation_flag_on(0)) return;
if (scr_process_deprecation_flag_on(1)) {
scr_warning_defer(w, scr_warning_throw_tick);
return;
}
}
scr_warning_defer(w, scr_warning_tick);
}
/* The console members' warnings. Node's emitWarning emits on
* process.nextTick, so these queue in order and each rides one raw tick
* on the shared tick queue; any left at exit are dropped with it. */
@@ -761,7 +866,8 @@ static void scr_warn_bad_arg(const char *arg, const char *want) {
* values: (warning: string | Error), then for string warnings a type
* string / ctor function / options object ({type, code, detail}) second
* and a code string / ctor function third. Wrong kinds throw Node's
* ERR_INVALID_ARG_TYPE TypeErrors; non-string details are ignored. */
* ERR_INVALID_ARG_TYPE TypeErrors at the call; non-string details are
* ignored. The warning itself is emitted on the next tick. */
void scr_process_emit_warning(ScrDyn *args) {
size_t argc = args->kind == SCR_DYN_ARR ? args->v.arr.len : 0;
ScrDyn *const *items = args->kind == SCR_DYN_ARR ? args->v.arr.items : NULL;
@@ -769,7 +875,7 @@ void scr_process_emit_warning(ScrDyn *args) {
/* An Error-encoded warning: type/code arguments are ignored (Node). */
if (warning != NULL && warning->kind == SCR_DYN_OBJ &&
scr_dyn_obj_get(warning, "%error", 6) != NULL) {
scr_warning_dispatch(warning);
scr_warning_emit_deferred(warning);
return;
}
if (warning == NULL || warning->kind != SCR_DYN_STR) {
@@ -824,7 +930,7 @@ void scr_process_emit_warning(ScrDyn *args) {
if (detail) {
scr_dyn_obj_set(w, "detail", 6, scr_dyn_new_str((ScrStr *)detail)); /* retains */
}
scr_warning_dispatch(w);
scr_warning_emit_deferred(w);
scr_dyn_release(w);
}
+10 -1
View File
@@ -2973,7 +2973,16 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
switch (cb->kind) {
case SCR_DYN_NULL: d = "null"; break;
case SCR_DYN_UNDEF: d = "undefined"; break;
case SCR_DYN_OBJ: d = "an instance of Object"; break;
case SCR_DYN_OBJ:
if (cb->null_proto) {
/* No constructor name: Node renders inspect(value, { depth: -1 }). */
bool keys = false;
for (size_t i = 0; i < cb->v.obj.len && !keys; i++) keys = cb->v.obj.entries[i].enumerable;
d = keys ? "[Object: null prototype]" : "[Object: null prototype] {}";
} else {
d = "an instance of Object";
}
break;
case SCR_DYN_ARR: d = "an instance of Array"; break;
case SCR_DYN_BYTES:
snprintf(detail, cap, "an instance of %s", cb->buffer ? "Buffer" : cb->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(cb->v.bytes->elem));
+32
View File
@@ -738,10 +738,41 @@ ScrStr *scr_env_get(const ScrStr *name) {
#endif
}
/* util.debuglog matches sections against NODE_DEBUG as the process
* started, so the first write to that variable records the value it
* replaces. */
static SCR_TL bool scr_node_debug_captured = false;
static SCR_TL char *scr_node_debug_value = NULL;
static void scr_node_debug_teardown(void) {
free(scr_node_debug_value);
scr_node_debug_value = NULL;
}
const char *scr_node_debug_initial(void) {
if (!scr_node_debug_captured) {
scr_node_debug_captured = true;
const char *value = scr_getenv("NODE_DEBUG");
if (value) {
size_t len = strlen(value);
scr_node_debug_value = malloc(len + 1);
if (!scr_node_debug_value) scr_trap("scriptc: out of memory\n");
memcpy(scr_node_debug_value, value, len + 1);
scr_atexit(scr_node_debug_teardown);
}
}
return scr_node_debug_value;
}
static void scr_node_debug_capture(const ScrStr *name) {
if (name->len == 10 && memcmp(name->data, "NODE_DEBUG", 10) == 0) (void)scr_node_debug_initial();
}
/* process.env.NAME = v — setenv(3): later scr_env_get reads and spawned
* children (posix_spawn inherits environ) observe the write, like Node.
* Both args borrowed (NUL-terminated ScrStr data); setenv copies. */
void scr_env_set(const ScrStr *name, const ScrStr *value) {
scr_node_debug_capture(name);
/* The environment holds UTF-8: a lone surrogate is stored as U+FFFD. */
char *name_owned, *value_owned;
const char *k = scr_str_utf8(name, &name_owned), *v = scr_str_utf8(value, &value_owned);
@@ -765,6 +796,7 @@ done:
/* `delete process.env.NAME` — unsetenv(3): later reads answer absent and
* spawned children lose the variable, like Node. Borrowed. */
void scr_env_unset(const ScrStr *name) {
scr_node_debug_capture(name);
#ifdef SCR_WORKERS
if (scr_context_env_set(name->data, NULL)) return;
#endif
+31 -5
View File
@@ -1634,6 +1634,17 @@ void scr_qs_parse_into(ScrMap *out, const ScrStr *qs, const ScrStr *sep,
* Borrows config; returns +1, or NULL with a Node-coded TypeError pending. */
struct ScrDyn *scr_util_parse_args(const struct ScrDyn *config);
ScrStr *scr_util_style_text(const struct ScrDyn *format, const struct ScrDyn *text, const struct ScrDyn *options);
/* Node's terminal color decision (FORCE_COLOR, NO_COLOR, TERM, CI hosts)
* once the stream is a terminal (scr_util_style.c). */
bool scr_util_terminal_colors(void);
/* util.inherits, util.deprecate, util.callbackify and util.debuglog
* (scr_util_callable.c). Arguments are borrowed; function results are +1
* native dyn functions, or NULL with Node's TypeError pending. */
void scr_util_inherits(struct ScrDyn *ctor, struct ScrDyn *super_ctor);
struct ScrDyn *scr_util_deprecate(struct ScrDyn *fn, struct ScrDyn *msg, struct ScrDyn *code,
struct ScrDyn *options);
struct ScrDyn *scr_util_callbackify(struct ScrDyn *original);
struct ScrDyn *scr_util_debuglog(struct ScrDyn *section, struct ScrDyn *callback);
/* Node/libuv system error utilities. Arguments are checked without coercion;
* entries is a fresh array of [number, [name, message]] pairs (+1). */
@@ -3501,6 +3512,8 @@ ScrStr *scr_env_get(const ScrStr *name);
* children observe the write, like Node. Both borrowed. Never throws. */
void scr_env_set(const ScrStr *name, const ScrStr *value);
void scr_env_unset(const ScrStr *name); /* delete process.env.NAME */
/* NODE_DEBUG as the process started (NULL when unset), for util.debuglog. */
const char *scr_node_debug_initial(void);
/* The whole environment as alternating [k, v, k, v, ...] strings in
* environ order — the compiler's process.env snapshot builds its record
* from this. +1 fresh array. Never throws. */
@@ -6108,11 +6121,11 @@ void scr_promise_mark_handled(ScrPromise *p);
ScrDyn *scr_promise_reason_dyn(const ScrPromise *p);
/* process warnings (scr_lib.c — always linked so any unit can emit a
* deprecation): dyn listeners plus Node's default stderr report
* ("(node:pid) [CODE] Name: message" and a detail second line). Emission
* is SYNCHRONOUS at the call (Node defers a tick — SEMANTICS.md 138's
* precedent). scr_process_emit_warning takes the ARGUMENT VECTOR as one
* dyn array and applies Node's full grammar/TypeErrors; scr_emit_warning
* is the C-side deprecation entry. */
* ("(node:pid) [CODE] Name: message" and a detail second line).
* scr_process_emit_warning takes the ARGUMENT VECTOR as one dyn array,
* applies Node's full grammar/TypeErrors at the call and emits on the
* next tick, as Node does; scr_emit_warning is the C-side entry and
* reports synchronously. */
void scr_process_on_warning(ScrDyn *fn);
void scr_process_off_warning(ScrDyn *fn);
void scr_process_emit_warning(ScrDyn *args);
@@ -6121,6 +6134,19 @@ void scr_emit_warning(const char *name, const char *code, ScrStr *message);
* listener registration): units that cannot link the dyn-async unit route
* their warnings through it, and print the default report otherwise. */
extern SCR_TL void (*scr_warning_emit_hook)(const char *name, const char *code, ScrStr *message);
/* process.noDeprecation (which 0) and process.throwDeprecation (which 1):
* the stored value (+1; undefined until assigned), the assignment
* (borrowed), and the truthiness the warning paths test. */
ScrDyn *scr_process_deprecation_flag(double which);
void scr_process_set_deprecation_flag(double which, ScrDyn *value);
bool scr_process_deprecation_flag_on(int which);
/* Node's emitWarning tail on the next tick (util.deprecate): a
* DeprecationWarning is dropped under noDeprecation and thrown from its
* own tick under throwDeprecation; otherwise the report and the 'warning'
* listeners run. scr_warning_new builds the string form's warning value
* (borrows; +1). */
void scr_warning_emit_deferred(ScrDyn *warning);
ScrDyn *scr_warning_new(ScrStr *message, const char *type, const ScrStr *code);
/* The promise-or-absent await's unit arm: one microtask hop, like JS's
* await of any non-thenable (and like awaiting a settled promise). */
void scr_await_hop(void);
+894
View File
@@ -0,0 +1,894 @@
/* node:util's function helpers over the checked-dynamic tree: inherits,
* deprecate, callbackify and debuglog. The returned wrappers are native
* dyn functions whose closures hold the wrapped values, so they keep
* Node's receiver forwarding, own properties and prototype links through
* every boxing of the same function. The compiled call site converts a
* wrapper back to the program's static function type when it has one.
*
* Warnings follow Node's emitWarning: deprecations are built at the call
* and reported on the next tick (scr_warning_emit_deferred), which also
* applies process.noDeprecation and process.throwDeprecation. debuglog
* matches sections against NODE_DEBUG as the process started and writes
* Node's "SECTION PID: message" lines to stderr. All arguments are
* borrowed. */
#include "scr_runtime.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static ScrBox *uc_box(ScrDyn *value /* moves in */) {
ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v);
scr_box_set_ref(box, value);
return box;
}
static ScrDyn *uc_cap(ScrClosure *closure, size_t index) {
return scr_box_get_ref(closure->caps[index]); /* +1 */
}
static void uc_set_cap(ScrClosure *closure, size_t index, ScrDyn *value /* moves in */) {
scr_box_set_ref(closure->caps[index], value);
}
static bool uc_nullish(const ScrDyn *value) {
return value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL;
}
static bool uc_object(const ScrDyn *value) {
switch (value->kind) {
case SCR_DYN_OBJ:
case SCR_DYN_ARR:
case SCR_DYN_FUNC:
case SCR_DYN_BYTES:
case SCR_DYN_HANDLE:
case SCR_DYN_PROMISE:
case SCR_DYN_TYPED_REF:
case SCR_DYN_PROXY:
return true;
default:
return false;
}
}
static void uc_type_error(const char *message) {
scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message));
}
static void uc_fence(const char *message) {
scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020");
}
/* `value[key]`: own and inherited members of object kinds, undefined for
* primitives (whose prototypes carry none of the keys read here). +1, or
* NULL with an exception pending. */
static ScrDyn *uc_get(const ScrDyn *value, const char *key) {
size_t len = strlen(key);
ScrDyn *result = NULL;
if (value->kind == SCR_DYN_FUNC) {
result = scr_dyn_fn_get(value, key, len);
} else if (value->kind == SCR_DYN_TYPED_REF) {
ScrDyn *view = scr_dyn_typed_ref_materialize(value);
if (scr_exc_pending()) {
scr_dyn_release(view);
return NULL;
}
result = uc_get(view, key);
scr_dyn_release(view);
return result;
} else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_PROXY) {
result = scr_dyn_obj_read(value, key, len);
} else {
return scr_dyn_retain(scr_dyn_undefined());
}
if (!result && !scr_exc_pending()) result = scr_dyn_retain(scr_dyn_undefined());
return result;
}
/* V8's rendering of a value inside a TypeError message: primitives as
* strings, objects as #<Object>. */
static ScrStr *uc_v8_value(const ScrDyn *value) {
if (value->kind == SCR_DYN_SYMBOL) return scr_insp_dyn((ScrDyn *)value, 0, 0);
if (uc_object(value)) return scr_str_new("#<Object>", 9);
ScrStr *text = scr_dyn_string_coerce_js(value);
return text ? text : scr_str_new("", 0);
}
static void uc_v8_value_error(const char *prefix, const ScrDyn *value, const char *suffix) {
ScrStr *text = uc_v8_value(value);
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, prefix);
scr_jb_put_str(&b, text);
scr_jb_puts(&b, suffix);
scr_str_release(text);
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
}
/* Reflect.apply(fn, receiver, args) — the receiver is bound for the call's
* synchronous extent, as for every dyn method call. +1 or NULL. */
static ScrDyn *uc_apply(ScrDyn *fn, ScrDyn *receiver, ScrDyn *const *args, size_t argc) {
if (!scr_dyn_is_callable(fn)) {
const char *kind = uc_object(fn) ? "an object" : fn->kind == SCR_DYN_NUM ? "a number"
: fn->kind == SCR_DYN_STR ? "a string" : fn->kind == SCR_DYN_BOOL ? "a boolean"
: fn->kind == SCR_DYN_NULL ? "an object" : fn->kind == SCR_DYN_SYMBOL ? "a symbol"
: fn->kind == SCR_DYN_BIGINT ? "a bigint" : "a undefined";
ScrStr *text = fn->kind == SCR_DYN_NULL ? scr_str_new("null", 4) : uc_v8_value(fn);
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "Function.prototype.apply was called on ");
scr_jb_put_str(&b, text);
scr_jb_puts(&b, ", which is ");
scr_jb_puts(&b, kind);
scr_jb_puts(&b, " and not a function");
scr_str_release(text);
scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b));
return NULL;
}
scr_dyn_this_push_dyn(receiver);
ScrDyn *result = scr_dyn_call(fn, args, argc, "function");
scr_dyn_this_pop();
return result;
}
/* Object.defineProperty(target, key, descriptor) over a fresh descriptor;
* the descriptor moves in. False with an exception pending on failure. */
static bool uc_define(ScrDyn *target, const char *key, ScrDyn *descriptor) {
ScrStr *name = scr_str_new(key, strlen(key));
ScrDyn *property = scr_dyn_new_str(name);
scr_str_release(name);
ScrDyn *defined = scr_dyn_define_property(target, property, descriptor);
scr_dyn_release(property);
scr_dyn_release(descriptor);
if (!defined) return false;
scr_dyn_release(defined);
return true;
}
/* ── util.inherits ─────────────────────────────────────────────────────
* Node 24's lib/util.js: nullish arguments and an undefined
* superCtor.prototype throw ERR_INVALID_ARG_TYPE; then super_ becomes a
* writable, configurable, non-enumerable own property and
* ctor.prototype's prototype becomes superCtor.prototype. */
void scr_util_inherits(ScrDyn *ctor, ScrDyn *super_ctor) {
if (uc_nullish(ctor)) {
scr_dyn_arg_type_fail("ctor", "of type function", ctor);
return;
}
if (uc_nullish(super_ctor)) {
scr_dyn_arg_type_fail("superCtor", "of type function", super_ctor);
return;
}
if ((ctor->kind == SCR_DYN_FUNC && ctor->v.fn.class_obj) ||
(super_ctor->kind == SCR_DYN_FUNC && super_ctor->v.fn.class_obj)) {
uc_fence("util.inherits over class constructors has no lowering; declare the subclass with extends");
return;
}
ScrDyn *super_prototype = uc_get(super_ctor, "prototype");
if (!super_prototype) return;
if (super_prototype->kind == SCR_DYN_UNDEF) {
scr_dyn_prop_type_fail("superCtor.prototype", "of type object", super_prototype);
scr_dyn_release(super_prototype);
return;
}
if (!uc_object(ctor)) {
uc_type_error("Object.defineProperty called on non-object");
scr_dyn_release(super_prototype);
return;
}
ScrDyn *descriptor = scr_dyn_new_obj();
scr_dyn_obj_set(descriptor, "value", 5, scr_dyn_retain(super_ctor));
scr_dyn_obj_set(descriptor, "writable", 8, scr_dyn_new_bool(true));
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
if (!uc_define(ctor, "super_", descriptor)) {
scr_dyn_release(super_prototype);
return;
}
ScrDyn *prototype = uc_get(ctor, "prototype");
if (prototype) {
if (uc_nullish(prototype)) {
uc_type_error("Object.setPrototypeOf called on null or undefined");
} else if (!uc_object(super_prototype) && super_prototype->kind != SCR_DYN_NULL) {
uc_v8_value_error("Object prototype may only be an Object or null: ", super_prototype, "");
} else if (uc_object(prototype)) {
scr_dyn_release(scr_dyn_set_prototype(prototype, super_prototype));
}
scr_dyn_release(prototype);
}
scr_dyn_release(super_prototype);
}
/* ── util.deprecate ────────────────────────────────────────────────────
* The wrapper warns once (and once per code across wrappers) before each
* call reaches fn with the call's receiver and arguments; construction
* reaches fn with the fresh receiver. codesWarned is process-wide, like
* Node's. */
static SCR_TL ScrDyn *uc_codes_warned = NULL;
static void uc_codes_warned_teardown(void) {
scr_dyn_release(uc_codes_warned);
uc_codes_warned = NULL;
}
enum { UC_DEP_FN, UC_DEP_MSG, UC_DEP_CODE, UC_DEP_WARNED, UC_DEP_CAPS };
/* process.emitWarning(msg, type, code) as deprecate's emitter calls it:
* a string message builds the warning, an Error is emitted as itself,
* anything else throws Node's ERR_INVALID_ARG_TYPE at the call. */
static bool uc_emit(ScrDyn *msg, const char *type, const ScrStr *code) {
if (!strcmp(type, "DeprecationWarning") && scr_process_deprecation_flag_on(0)) return true;
ScrDyn *warning = NULL;
if (msg->kind == SCR_DYN_STR) {
warning = scr_warning_new(msg->v.str, type, code);
} else if (scr_dyn_ref_is_error(msg)) {
warning = scr_dyn_retain(msg);
} else {
scr_dyn_arg_type_fail("warning", "of type string or an instance of Error", msg);
return false;
}
scr_warning_emit_deferred(warning);
scr_dyn_release(warning);
return true;
}
static bool uc_deprecation_warn(ScrClosure *closure) {
if (scr_box_get_f64(closure->caps[UC_DEP_WARNED]) != 0) return true;
scr_box_set_f64(closure->caps[UC_DEP_WARNED], 1);
ScrDyn *msg = uc_cap(closure, UC_DEP_MSG);
ScrDyn *code = uc_cap(closure, UC_DEP_CODE);
bool ok = true;
if (code->kind == SCR_DYN_STR && code->v.str->len == 19 &&
memcmp(code->v.str->data, "ExperimentalWarning", 19) == 0) {
ok = uc_emit(msg, "ExperimentalWarning", NULL);
} else if (code->kind == SCR_DYN_STR) {
if (!uc_codes_warned) {
uc_codes_warned = scr_dyn_new_obj_null_proto();
scr_atexit(uc_codes_warned_teardown);
}
const ScrStr *key = code->v.str;
if (!scr_dyn_obj_get(uc_codes_warned, key->data, key->len)) {
ok = uc_emit(msg, "DeprecationWarning", key);
if (ok) scr_dyn_obj_set(uc_codes_warned, key->data, key->len, scr_dyn_new_bool(true));
}
} else {
ok = uc_emit(msg, "DeprecationWarning", NULL);
}
scr_dyn_release(code);
scr_dyn_release(msg);
return ok;
}
static ScrDyn *uc_deprecated_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
if (!scr_process_deprecation_flag_on(0) && !uc_deprecation_warn(closure)) return NULL;
ScrDyn *fn = uc_cap(closure, UC_DEP_FN);
ScrDyn *receiver = scr_dyn_this_get();
ScrDyn *result = uc_apply(fn, receiver, args, argc);
scr_dyn_release(receiver);
scr_dyn_release(fn);
return result;
}
ScrDyn *scr_util_deprecate(ScrDyn *fn, ScrDyn *msg, ScrDyn *code, ScrDyn *options) {
/* The `{ modifyPrototype } = {}` parameter binds before the body runs. */
bool modify_prototype = true;
if (options->kind == SCR_DYN_NULL) {
uc_type_error("Cannot destructure property 'modifyPrototype' of "
"'(intermediate value)(intermediate value)(intermediate value)' as it is null.");
return NULL;
}
if (uc_object(options)) {
ScrDyn *modify = uc_get(options, "modifyPrototype");
if (!modify) return NULL;
if (modify->kind != SCR_DYN_UNDEF) modify_prototype = scr_dyn_truthy(modify);
scr_dyn_release(modify);
}
if (code->kind != SCR_DYN_UNDEF && code->kind != SCR_DYN_STR) {
scr_dyn_arg_type_fail("code", "of type string", code);
return NULL;
}
if (fn->kind == SCR_DYN_FUNC && fn->v.fn.class_obj) {
uc_fence("util.deprecate over class constructors has no lowering; deprecate a factory function instead");
return NULL;
}
ScrClosure *closure = scr_closure_new(NULL, UC_DEP_CAPS);
closure->function_kind = 4; /* an ordinary function with its own prototype */
closure->caps[UC_DEP_FN] = uc_box(scr_dyn_retain(fn));
closure->caps[UC_DEP_MSG] = uc_box(scr_dyn_retain(msg));
closure->caps[UC_DEP_CODE] = uc_box(scr_dyn_retain(code));
closure->caps[UC_DEP_WARNED] = scr_box_new(SCR_BOX_F64);
ScrDyn *deprecated = scr_dyn_new_func(closure, uc_deprecated_call, 0, "native:util.deprecate", "deprecated");
if (!modify_prototype) return deprecated;
/* ObjectSetPrototypeOf(deprecated, fn), then the shared prototype and
* fn's own length descriptor. */
if (fn->kind != SCR_DYN_NULL && !uc_object(fn)) {
uc_v8_value_error("Object prototype may only be an Object or null: ", fn, "");
scr_dyn_release(deprecated);
return NULL;
}
ScrDyn *linked = scr_dyn_set_prototype(deprecated, fn);
if (!linked) {
scr_dyn_release(deprecated);
return NULL;
}
scr_dyn_release(linked);
if (fn->kind == SCR_DYN_NULL) {
uc_type_error("Cannot read properties of null (reading 'prototype')");
scr_dyn_release(deprecated);
return NULL;
}
ScrDyn *prototype = uc_get(fn, "prototype");
if (!prototype) {
scr_dyn_release(deprecated);
return NULL;
}
if (scr_dyn_truthy(prototype)) {
ScrStr *key = scr_str_new("prototype", 9);
scr_dyn_key_set(deprecated, key, prototype);
scr_str_release(key);
}
scr_dyn_release(prototype);
if (scr_exc_pending()) {
scr_dyn_release(deprecated);
return NULL;
}
ScrStr *length_name = scr_str_new("length", 6);
ScrDyn *length_key = scr_dyn_new_str(length_name);
scr_str_release(length_name);
ScrDyn *length = scr_dyn_get_own_property_descriptor(fn, length_key);
scr_dyn_release(length_key);
if (!length || (length->kind == SCR_DYN_OBJ && !uc_define(deprecated, "length", scr_dyn_retain(length)))) {
scr_dyn_release(length);
scr_dyn_release(deprecated);
return NULL;
}
scr_dyn_release(length);
return deprecated;
}
/* ── util.callbackify ──────────────────────────────────────────────────
* The wrapper pops the callback, binds it to the call's receiver and
* calls original; the promise's settlement schedules the callback on the
* next tick, so a throwing callback is an uncaught exception there. A
* falsy rejection reason arrives wrapped in ERR_FALSY_VALUE_REJECTION
* whose `reason` is the original value. */
/* process.nextTick(cb, null, value) or the rejection's cb(err). */
static void uc_callback_tick(ScrClosure *closure) {
ScrDyn *cb = uc_cap(closure, 0);
ScrDyn *err = uc_cap(closure, 1);
ScrDyn *value = uc_cap(closure, 2);
ScrDyn *args[2] = {err, value};
size_t argc = (size_t)scr_box_get_f64(closure->caps[3]);
ScrDyn *result = scr_dyn_call(cb, args, argc, "callback");
scr_dyn_release(result);
scr_dyn_release(value);
scr_dyn_release(err);
scr_dyn_release(cb);
}
static void uc_schedule_callback(ScrDyn *cb, ScrDyn *err /* moves in */, ScrDyn *value /* moves in */,
size_t argc) {
ScrClosure *tick = scr_closure_new((void *)uc_callback_tick, 4);
tick->caps[0] = uc_box(scr_dyn_retain(cb));
tick->caps[1] = uc_box(err);
tick->caps[2] = uc_box(value);
tick->caps[3] = scr_box_new(SCR_BOX_F64);
scr_box_set_f64(tick->caps[3], (double)argc);
scr_next_tick(tick);
}
static ScrDyn *uc_callbackify_fulfilled(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrDyn *cb = uc_cap(closure, 0);
uc_schedule_callback(cb, scr_dyn_new_null(), scr_dyn_retain(argc ? args[0] : scr_dyn_undefined()), 2);
scr_dyn_release(cb);
return scr_dyn_retain(scr_dyn_undefined());
}
static ScrDyn *uc_callbackify_rejected(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrDyn *cb = uc_cap(closure, 0);
ScrDyn *reason = argc ? args[0] : scr_dyn_undefined();
ScrDyn *err;
if (scr_dyn_truthy(reason)) {
err = scr_dyn_retain(reason);
} else {
static const char message[] = "Promise was rejected with falsy value";
ScrStr *text = scr_str_new(message, sizeof message - 1);
ScrError *e = scr_error_new(SCR_ERR_ERROR, text);
scr_str_release(text);
scr_error_set_code(e, "ERR_FALSY_VALUE_REJECTION");
err = scr_dyn_from_error(e);
scr_error_release(e);
scr_dyn_obj_set(err, "reason", 6, scr_dyn_retain(reason));
}
uc_schedule_callback(cb, err, scr_dyn_retain(scr_dyn_undefined()), 1);
scr_dyn_release(cb);
return scr_dyn_retain(scr_dyn_undefined());
}
static ScrDyn *uc_reaction(ScrDyn *cb, ScrDynThunk thunk) {
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->caps[0] = uc_box(scr_dyn_retain(cb));
return scr_dyn_new_func(closure, thunk, 1, "native:util.callbackify", "");
}
static void uc_last_argument_fail(const ScrDyn *got) {
char detail[256];
const char *received = scr_dyn_specific_type(got, detail, sizeof detail);
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, "The last argument must be of type function. Received ");
scr_jb_puts(&b, received);
ScrStr *message = scr_jb_finish(&b);
scr_throw_error_msg_code(SCR_ERR_TYPE, message->data, message->len, "ERR_INVALID_ARG_TYPE");
scr_str_release(message);
}
static ScrDyn *uc_callbackified_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrDyn *maybe_cb = argc ? args[argc - 1] : scr_dyn_undefined();
if (maybe_cb->kind == SCR_DYN_PROXY && scr_dyn_is_callable(maybe_cb)) {
uc_fence("util.callbackify callbacks that are Proxy values have no lowering");
return NULL;
}
if (maybe_cb->kind != SCR_DYN_FUNC) {
uc_last_argument_fail(maybe_cb);
return NULL;
}
ScrDyn *receiver = scr_dyn_this_get();
ScrDyn *bind_args[1] = {receiver};
ScrDyn *cb = scr_dyn_bind(maybe_cb, bind_args, 1);
ScrDyn *original = uc_cap(closure, 0);
ScrDyn *returned = uc_apply(original, receiver, args, argc ? argc - 1 : 0);
scr_dyn_release(original);
scr_dyn_release(receiver);
if (!returned) {
scr_dyn_release(cb);
return NULL;
}
ScrDyn *on_fulfilled = uc_reaction(cb, uc_callbackify_fulfilled);
ScrDyn *on_rejected = uc_reaction(cb, uc_callbackify_rejected);
scr_dyn_release(cb);
ScrDyn *chained = NULL;
ScrPromise *promise = scr_dyn_promise_of(returned);
if (promise) {
chained = scr_dyn_promise_then(promise, on_fulfilled, on_rejected, NULL);
} else if (uc_nullish(returned)) {
uc_type_error(returned->kind == SCR_DYN_NULL ? "Cannot read properties of null (reading 'then')"
: "Cannot read properties of undefined (reading 'then')");
} else {
ScrDyn *then = uc_get(returned, "then");
if (then && scr_dyn_is_callable(then)) {
ScrDyn *reactions[2] = {on_fulfilled, on_rejected};
chained = uc_apply(then, returned, reactions, 2);
} else if (then) {
uc_type_error("ReflectApply(...).then is not a function");
}
scr_dyn_release(then);
}
scr_dyn_release(on_rejected);
scr_dyn_release(on_fulfilled);
scr_dyn_release(returned);
if (!chained) return NULL;
scr_dyn_release(chained);
return scr_dyn_retain(scr_dyn_undefined());
}
ScrDyn *scr_util_callbackify(ScrDyn *original) {
if (!scr_dyn_is_callable(original)) {
scr_dyn_arg_type_fail("original", "of type function", original);
return NULL;
}
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->function_kind = 4; /* an ordinary function with its own prototype */
closure->caps[0] = uc_box(scr_dyn_retain(original));
ScrDyn *callbackified =
scr_dyn_new_func(closure, uc_callbackified_call, 0, "native:util.callbackify", "callbackified");
/* original's own descriptors, with a numeric length one longer and a
* string name suffixed "Callbackified". */
ScrDyn *descriptors = scr_dyn_get_own_property_descriptors(original);
if (!descriptors) {
scr_dyn_release(callbackified);
return NULL;
}
if (descriptors->kind == SCR_DYN_OBJ) {
ScrDyn *length = scr_dyn_obj_get(descriptors, "length", 6);
const ScrDyn *length_value = length && length->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(length, "value", 5) : NULL;
if (length_value && length_value->kind == SCR_DYN_NUM)
scr_dyn_obj_set(length, "value", 5, scr_dyn_new_num(length_value->v.num + 1));
ScrDyn *name = scr_dyn_obj_get(descriptors, "name", 4);
const ScrDyn *name_value = name && name->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(name, "value", 5) : NULL;
if (name_value && name_value->kind == SCR_DYN_STR) {
ScrStr *suffix = scr_str_new("Callbackified", 13);
ScrStr *joined = scr_str_concat(name_value->v.str, suffix);
scr_str_release(suffix);
scr_dyn_obj_set(name, "value", 5, scr_dyn_new_str(joined));
scr_str_release(joined);
}
}
ScrDyn *defined = scr_dyn_define_props(callbackified, descriptors);
scr_dyn_release(descriptors);
if (!defined) {
scr_dyn_release(callbackified);
return NULL;
}
scr_dyn_release(defined);
return callbackified;
}
/* ── util.format over dyn arguments ────────────────────────────────────
* formatWithOptionsInternal for debuglog's lines: the %-substitutions
* (%s %d %i %f %j %o %O %c %%) with Node's argument cursor, then the
* remaining arguments space-joined, strings verbatim and everything else
* inspected. With terminal colors, primitives take inspect's default
* styles; colored object renderings have no lowering. */
static bool uc_primitive(const ScrDyn *value) {
return !uc_object(value);
}
static ScrStr *uc_inspect(ScrDyn *value, double depth, bool colors) {
if (!colors) return scr_insp_dyn(value, 0, depth);
if (!uc_primitive(value)) {
uc_fence("util.debuglog output of objects on a color terminal has no lowering");
return NULL;
}
const char *open = "\x1b[33m", *close = "\x1b[39m";
if (value->kind == SCR_DYN_UNDEF) open = "\x1b[90m";
else if (value->kind == SCR_DYN_NULL) open = "\x1b[1m", close = "\x1b[22m";
else if (value->kind == SCR_DYN_STR || value->kind == SCR_DYN_SYMBOL) open = "\x1b[32m";
ScrStr *text = scr_insp_dyn(value, 0, depth);
ScrJsonBuf b;
scr_jb_init(&b);
scr_jb_puts(&b, open);
scr_jb_put_str(&b, text);
scr_jb_puts(&b, close);
scr_str_release(text);
return scr_jb_finish(&b);
}
/* hasBuiltInToString's negative answer for checked objects: an own or
* inherited toString the program defined. Symbol-keyed members (where
* Symbol.toPrimitive would also count) are reported as unknown. */
static int uc_custom_to_string(const ScrDyn *value) {
for (const ScrDyn *current = value; current && current->kind == SCR_DYN_OBJ; current = current->prototype) {
if (current->symbol_properties) return -1;
const ScrDyn *member = scr_dyn_obj_get(current, "toString", 8);
if (member) return member->kind == SCR_DYN_FUNC;
}
return 0;
}
static ScrStr *uc_format_s(ScrDyn *value) {
if (value->kind == SCR_DYN_STR) return scr_str_retain(value->v.str);
if (value->kind == SCR_DYN_NUM) return scr_insp_f64(value->v.num);
if (value->kind == SCR_DYN_FUNC) {
uc_fence("util.debuglog %s of a function prints its source text, which a compiled binary does not keep");
return NULL;
}
if (value->kind == SCR_DYN_OBJ && scr_dyn_obj_get(value, "%error", 6) == NULL) {
int custom = uc_custom_to_string(value);
if (custom < 0) {
uc_fence("util.debuglog %s of objects with symbol-keyed members has no lowering");
return NULL;
}
if (custom) return scr_dyn_string_coerce_js(value);
}
return scr_insp_dyn_s(value, 0);
}
static ScrStr *uc_format_number(ScrDyn *value, int spec) {
if (value->kind == SCR_DYN_BIGINT && spec != 'f') return scr_bigint_inspect(value->v.bigint);
if (value->kind == SCR_DYN_SYMBOL) return scr_str_new("NaN", 3);
double number;
if (spec == 'd') {
if (value->kind == SCR_DYN_BIGINT) return scr_bigint_inspect(value->v.bigint);
if (!scr_dyn_number_coerce_js(value, &number)) return NULL;
} else {
ScrStr *text = scr_dyn_string_coerce_js(value);
if (!text) return NULL;
number = spec == 'i' ? scr_parse_int(text, 0) : scr_parse_float(text);
scr_str_release(text);
}
return scr_insp_f64(number);
}
static ScrStr *uc_format(ScrDyn *const *args, size_t argc, bool colors) {
if (argc == 0) return scr_str_new("", 0);
ScrJsonBuf out;
scr_jb_init(&out);
size_t a = 0;
const char *join = "";
if (args[0]->kind == SCR_DYN_STR) {
const ScrStr *first = args[0]->v.str;
if (argc == 1) {
scr_str_release(scr_jb_finish(&out));
return scr_str_retain((ScrStr *)first);
}
const char *text = first->data;
size_t last = 0;
for (size_t i = 0; first->len > 0 && i < first->len - 1; i++) {
if (text[i] != '%') continue;
int next = (unsigned char)text[++i];
if (a + 1 != argc) {
ScrStr *piece = NULL;
switch (next) {
case 's': piece = uc_format_s(args[++a]); break;
case 'j': piece = scr_dyn_format_j(args[++a]); break;
case 'd':
case 'i':
case 'f': piece = uc_format_number(args[++a], next); break;
case 'O': piece = uc_inspect(args[++a], 2, colors); break;
case 'o':
if (!uc_primitive(args[a + 1])) {
uc_fence("util.debuglog %o of objects renders hidden properties, which have no lowering; use %O");
break;
}
piece = uc_inspect(args[++a], 4, colors);
break;
case 'c':
a += 1;
piece = scr_str_new("", 0);
break;
case '%':
scr_jb_write(&out, text + last, i - last);
last = i + 1;
continue;
default:
continue;
}
if (!piece) {
scr_str_release(scr_jb_finish(&out));
return NULL;
}
if (last != i - 1) scr_jb_write(&out, text + last, i - 1 - last);
scr_jb_put_str(&out, piece);
scr_str_release(piece);
last = i + 1;
} else if (next == '%') {
scr_jb_write(&out, text + last, i - last);
last = i + 1;
}
}
if (last != 0) {
a++;
join = " ";
if (last < first->len) scr_jb_write(&out, text + last, first->len - last);
}
}
for (; a < argc; a++) {
scr_jb_puts(&out, join);
if (args[a]->kind == SCR_DYN_STR) {
scr_jb_put_str(&out, args[a]->v.str);
} else {
ScrStr *piece = uc_inspect(args[a], 2, colors);
if (!piece) {
scr_str_release(scr_jb_finish(&out));
return NULL;
}
scr_jb_put_str(&out, piece);
scr_str_release(piece);
}
join = " ";
}
return scr_jb_finish(&out);
}
/* ── util.debuglog ─────────────────────────────────────────────────────
* The logger defers its NODE_DEBUG test to its first call or `enabled`
* read. The first call also selects the section's shared implementation
* (a writer for enabled sections, a no-op otherwise), hands it to the
* callback with its own `enabled` getter, and forwards every later call
* while the section is enabled. */
enum { UC_LOG_SET, UC_LOG_CB, UC_LOG_ENABLED, UC_LOG_DEBUG, UC_LOG_TESTED, UC_LOG_CAPS };
static SCR_TL ScrDyn *uc_debug_impls = NULL;
static SCR_TL ScrRegex *uc_debug_pattern = NULL;
static SCR_TL bool uc_debug_pattern_ready = false;
static void uc_debug_teardown(void) {
scr_dyn_release(uc_debug_impls);
uc_debug_impls = NULL;
scr_regex_release(uc_debug_pattern);
uc_debug_pattern = NULL;
uc_debug_pattern_ready = false;
}
static void uc_debug_register(void) {
static SCR_TL bool registered = false;
if (registered) return;
registered = true;
scr_atexit(uc_debug_teardown);
}
/* initializeDebugEnv: ^name$ alternatives over the comma list, with '*'
* as a wildcard and the other regexp syntax escaped, case-insensitive. */
static bool uc_debug_test(const ScrStr *section) {
if (!uc_debug_pattern_ready) {
uc_debug_pattern_ready = true;
uc_debug_register();
const char *env = scr_node_debug_initial();
if (env && *env) {
ScrJsonBuf pattern;
scr_jb_init(&pattern);
scr_jb_putc(&pattern, '^');
for (const char *p = env; *p; p++) {
if (strchr("|\\{}()[]^$+?.", *p)) {
scr_jb_putc(&pattern, '\\');
scr_jb_putc(&pattern, *p);
} else if (*p == '*') {
scr_jb_puts(&pattern, ".*");
} else if (*p == ',') {
scr_jb_puts(&pattern, "$|^");
} else {
scr_jb_putc(&pattern, *p);
}
}
scr_jb_putc(&pattern, '$');
ScrStr *source = scr_jb_finish(&pattern);
ScrStr *flags = scr_str_new("i", 1);
uc_debug_pattern = scr_regex_new(source, flags);
scr_str_release(flags);
scr_str_release(source);
if (scr_exc_pending()) return false;
}
}
return uc_debug_pattern != NULL && scr_regex_test(uc_debug_pattern, (ScrStr *)section);
}
static ScrDyn *uc_debug_write(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
bool colors = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors()
: scr_process_is_tty(2) && scr_util_terminal_colors();
ScrStr *message = uc_format(args, argc, colors);
if (!message) return NULL;
ScrDyn *set = uc_cap(closure, 0);
char pid[64];
long id = (long)scr_process_pid();
if (colors) snprintf(pid, sizeof pid, "\x1b[33m%ld\x1b[39m", id);
else snprintf(pid, sizeof pid, "%ld", id);
ScrJsonBuf line;
scr_jb_init(&line);
scr_jb_put_str(&line, set->v.str);
scr_jb_putc(&line, ' ');
scr_jb_puts(&line, pid);
scr_jb_puts(&line, ": ");
scr_jb_put_str(&line, message);
scr_jb_putc(&line, '\n');
ScrStr *text = scr_jb_finish(&line);
scr_process_stderr_write(text);
scr_str_release(text);
scr_str_release(message);
scr_dyn_release(set);
return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined());
}
static ScrDyn *uc_debug_noop(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
(void)closure; (void)args; (void)argc;
return scr_dyn_retain(scr_dyn_undefined());
}
/* debuglogImpl: one implementation per upper-cased section. */
static ScrDyn *uc_debug_impl(bool enabled, ScrDyn *set) {
uc_debug_register();
if (!uc_debug_impls) uc_debug_impls = scr_dyn_new_obj_null_proto();
const ScrStr *key = set->v.str;
ScrDyn *impl = scr_dyn_obj_get(uc_debug_impls, key->data, key->len);
if (impl) return scr_dyn_retain(impl);
if (enabled) {
if ((key->len == 4 && !memcmp(key->data, "HTTP", 4)) || (key->len == 5 && !memcmp(key->data, "HTTP2", 5))) {
ScrJsonBuf warning;
scr_jb_init(&warning);
scr_jb_puts(&warning, "Setting the NODE_DEBUG environment variable to '");
scr_jb_puts(&warning, key->len == 4 ? "http" : "http2");
scr_jb_puts(&warning, "' can expose sensitive data (such as passwords, tokens and authentication headers) in the resulting log.");
ScrStr *text = scr_jb_finish(&warning);
scr_process_emit_warning_text(text);
scr_str_release(text);
}
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->function_kind = 4;
closure->caps[0] = uc_box(scr_dyn_retain(set));
impl = scr_dyn_new_func(closure, uc_debug_write, 0, "native:util.debuglog", "debug");
} else {
impl = scr_dyn_new_func(scr_closure_new(NULL, 0), uc_debug_noop, 0, "native:util.debuglog", "noop");
}
scr_dyn_obj_set(uc_debug_impls, key->data, key->len, scr_dyn_retain(impl));
return impl;
}
/* init(): upper-case the section and test it. */
static bool uc_debug_init(ScrClosure *closure) {
ScrDyn *set = uc_cap(closure, UC_LOG_SET);
if (uc_nullish(set)) {
scr_dyn_release(set);
uc_type_error("String.prototype.toUpperCase called on null or undefined");
return false;
}
ScrStr *text = scr_dyn_string_coerce_js(set);
scr_dyn_release(set);
if (!text) return false;
ScrStr *upper = scr_str_to_upper(text);
scr_str_release(text);
uc_set_cap(closure, UC_LOG_SET, scr_dyn_new_str(upper));
bool enabled = uc_debug_test(upper);
scr_str_release(upper);
if (scr_exc_pending()) return false;
uc_set_cap(closure, UC_LOG_ENABLED, scr_dyn_new_bool(enabled));
return true;
}
static ScrDyn *uc_enabled_getter(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
(void)args; (void)argc;
return uc_cap(closure, 0);
}
static ScrDyn *uc_enabled_accessor(ScrBox *enabled) {
ScrClosure *closure = scr_closure_new(NULL, 1);
closure->caps[0] = scr_box_retain(enabled);
return scr_dyn_new_func(closure, uc_enabled_getter, 0, "native:util.debuglog", "get");
}
static bool uc_define_enabled(ScrDyn *target, ScrDyn *getter /* moves in */) {
ScrDyn *descriptor = scr_dyn_new_obj();
scr_dyn_obj_set(descriptor, "get", 3, getter);
scr_dyn_obj_set(descriptor, "configurable", 12, scr_dyn_new_bool(true));
scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(true));
return uc_define(target, "enabled", descriptor);
}
static ScrDyn *uc_logger_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
ScrDyn *enabled = uc_cap(closure, UC_LOG_ENABLED);
bool disabled = enabled->kind == SCR_DYN_BOOL && !enabled->v.b;
scr_dyn_release(enabled);
if (disabled) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *debug = uc_cap(closure, UC_LOG_DEBUG);
if (debug->kind == SCR_DYN_UNDEF) {
/* The first call: select the implementation, then the callback. */
if (!uc_debug_init(closure)) return NULL;
enabled = uc_cap(closure, UC_LOG_ENABLED);
ScrDyn *set = uc_cap(closure, UC_LOG_SET);
debug = uc_debug_impl(enabled->v.b, set);
scr_dyn_release(set);
scr_dyn_release(enabled);
uc_set_cap(closure, UC_LOG_DEBUG, scr_dyn_retain(debug));
ScrDyn *cb = uc_cap(closure, UC_LOG_CB);
if (cb->kind == SCR_DYN_FUNC) {
bool ok = uc_define_enabled(debug, uc_enabled_accessor(closure->caps[UC_LOG_ENABLED]));
ScrDyn *result = ok ? scr_dyn_call(cb, &debug, 1, "callback") : NULL;
scr_dyn_release(result);
if (!result) {
scr_dyn_release(cb);
scr_dyn_release(debug);
return NULL;
}
}
scr_dyn_release(cb);
}
ScrDyn *result = scr_dyn_call(debug, args, argc, "debug");
scr_dyn_release(debug);
return result;
}
static ScrDyn *uc_logger_enabled(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
(void)args; (void)argc;
if (scr_box_get_f64(closure->caps[UC_LOG_TESTED]) == 0) {
if (!uc_debug_init(closure)) return NULL;
scr_box_set_f64(closure->caps[UC_LOG_TESTED], 1);
}
return uc_cap(closure, UC_LOG_ENABLED);
}
ScrDyn *scr_util_debuglog(ScrDyn *section, ScrDyn *callback) {
ScrClosure *closure = scr_closure_new(NULL, UC_LOG_CAPS);
closure->caps[UC_LOG_SET] = uc_box(scr_dyn_retain(section));
closure->caps[UC_LOG_CB] = uc_box(scr_dyn_retain(callback));
closure->caps[UC_LOG_ENABLED] = uc_box(scr_dyn_retain(scr_dyn_undefined()));
closure->caps[UC_LOG_DEBUG] = uc_box(scr_dyn_retain(scr_dyn_undefined()));
closure->caps[UC_LOG_TESTED] = scr_box_new(SCR_BOX_F64);
/* The `enabled` getter shares the logger's state boxes. */
ScrClosure *getter = scr_closure_new(NULL, UC_LOG_CAPS);
for (size_t i = 0; i < UC_LOG_CAPS; i++) getter->caps[i] = scr_box_retain(closure->caps[i]);
ScrDyn *logger = scr_dyn_new_func(closure, uc_logger_call, 0, "native:util.debuglog", "logger");
ScrDyn *get = scr_dyn_new_func(getter, uc_logger_enabled, 0, "native:util.debuglog", "get");
if (!uc_define_enabled(logger, get)) {
scr_dyn_release(logger);
return NULL;
}
return logger;
}
+3 -3
View File
@@ -173,7 +173,7 @@ done:
/* styleText only needs the color/no-color distinction, rather than the
* full 4/8/24-bit depth. Follow the ordering of Node 24 internal/tty. */
static bool util_style_terminal_colors(void) {
bool scr_util_terminal_colors(void) {
const char *force = scr_getenv("FORCE_COLOR");
if (force) {
bool enabled = !strcmp(force, "") || !strcmp(force, "true") ||
@@ -294,8 +294,8 @@ ScrStr *scr_util_style_text(const ScrDyn *format, const ScrDyn *text, const ScrD
scr_dyn_arg_type_fail("stream", "an instance of ReadableStream, WritableStream, or Stream", stream); goto done;
}
}
colorize = scr_getenv("FORCE_COLOR") ? util_style_terminal_colors() :
scr_process_is_tty(fd) && util_style_terminal_colors();
colorize = scr_getenv("FORCE_COLOR") ? scr_util_terminal_colors() :
scr_process_is_tty(fd) && scr_util_terminal_colors();
}
}
scr_dyn_release(view); view = util_style_view(format);