mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
The max-listeners argument ladders answer Node's exact errors
- Instance setMaxListeners over a non-number routes through the checked-dynamic ladder (emitter.setMaxChk): ERR_INVALID_ARG_TYPE with the rendered Received tail, the existing ERR_OUT_OF_RANGE range gate for numbers, and a well-typed dyn still applies.
- events.defaultMaxListeners is a real module property now: reads answer the process-wide default, writes (statement and expression position) validate through emitter.setDefaultMaxChk with Node's own message slot ("defaultMaxListeners"); the qualified static EventEmitter.setMaxListeners(n) shares the ladder under its "setMaxListeners" slot, and the per-target static form with a provably-non-emitter target throws ERR_INVALID_ARG_TYPE on "eventTargets".
- error.argTypeThrow joins error.nodeThrow as the generic always-throwing lowered arm whose Received tail renders at runtime.
- Corpus 2560 pins the ladders byte-identically on both backends.
This commit is contained in:
@@ -4294,6 +4294,12 @@ export function emitExpr(E: CEmitter, e: IrExpr): Temp {
|
||||
return finish(`scr_buffer_new_string_fail(${arg(0)})`);
|
||||
case "fs.toUnixTimestamp":
|
||||
return finish(`scr_fs_to_unix_timestamp(${arg(0)})`);
|
||||
case "error.argTypeThrow":
|
||||
// Always throws with the runtime-rendered Received tail (the
|
||||
// error.nodeThrow dummy pattern). Borrows all three.
|
||||
return finish(
|
||||
`(scr_throw_arg_type(${arg(0)}, ${arg(1)}, ${arg(2)}), ${isRefCounted(e.type) ? `(${cType(e.type).trim()})NULL` : "0"})`,
|
||||
);
|
||||
// The fs Buffer forms (scr_bytes_io.c): the sync pair throws
|
||||
// like the utf8 forms (may-throw seed set); the promise form
|
||||
// rejects instead.
|
||||
@@ -4860,10 +4866,17 @@ export function emitExpr(E: CEmitter, e: IrExpr): Temp {
|
||||
return finish(
|
||||
`(${cType(e.type).trim()})scr_emitter_set_max((ScrEmitter *)${arg(0)}, ${arg(1)})`,
|
||||
);
|
||||
case "emitter.setMaxChk":
|
||||
// The checked-dynamic ladder (may throw); +1 receiver back.
|
||||
return finish(
|
||||
`(${cType(e.type).trim()})scr_emitter_set_max_chk((ScrEmitter *)${arg(0)}, ${arg(1)})`,
|
||||
);
|
||||
case "emitter.getMax":
|
||||
return finish(`scr_emitter_get_max((ScrEmitter *)${arg(0)})`);
|
||||
case "emitter.setDefaultMax":
|
||||
return finish(`scr_emitter_set_default_max(${arg(0)})`);
|
||||
case "emitter.setDefaultMaxChk":
|
||||
return finish(`scr_emitter_set_default_max_chk(${arg(0)}, ${arg(1)})`);
|
||||
case "emitter.getDefaultMax":
|
||||
return finish(`scr_emitter_get_default_max()`);
|
||||
// node:stream (scr_stream.c — linked exactly when these appear,
|
||||
|
||||
@@ -509,6 +509,8 @@ const LIB_FN_SYMS: Record<string, string> = {
|
||||
"emitter.names": "scr_emitter_event_names",
|
||||
"emitter.listeners": "scr_emitter_listeners",
|
||||
"emitter.setMax": "scr_emitter_set_max",
|
||||
"emitter.setMaxChk": "scr_emitter_set_max_chk",
|
||||
"emitter.setDefaultMaxChk": "scr_emitter_set_default_max_chk",
|
||||
"emitter.getMax": "scr_emitter_get_max",
|
||||
"emitter.setDefaultMax": "scr_emitter_set_default_max",
|
||||
"emitter.getDefaultMax": "scr_emitter_get_default_max",
|
||||
@@ -9289,6 +9291,24 @@ class LlEmitter {
|
||||
// Loop liveness first (one table for generic and special shapes).
|
||||
if (USES_TIMERS_LIB_FNS.has(e.fn)) this.usesTimers = true;
|
||||
// The handful with non-generic shapes first.
|
||||
if (e.fn === "error.argTypeThrow") {
|
||||
// Always throws with the runtime-rendered Received tail (the
|
||||
// error.nodeThrow dummy pattern). Borrows all three.
|
||||
const an = this.emitExpr(e.args[0]!);
|
||||
const ex = this.emitExpr(e.args[1]!);
|
||||
const got = this.emitExpr(e.args[2]!);
|
||||
this.declare(`declare void @scr_throw_arg_type(ptr, ptr, ptr)`);
|
||||
B.line(`call void @scr_throw_arg_type(ptr ${an.name}, ptr ${ex.name}, ptr ${got.name})`);
|
||||
const ty = this.llType(e.type);
|
||||
if (ty === "void") {
|
||||
this.emitPendingCheck();
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const dummy = ty === "double" ? f64Lit(0) : ty === "i1" ? "false" : "null";
|
||||
const out = this.own({ name: dummy, type: e.type });
|
||||
this.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
if (e.fn === "error.nodeThrow") {
|
||||
// The compiler-resolved Node-parity throw (always throws — the
|
||||
// typed dummy is abandoned by the pending check's unwind).
|
||||
|
||||
@@ -507,6 +507,21 @@ export function lowerEmitterMethodCall(L: Lowerer, call: ts.CallExpression,
|
||||
// everything else fences by its static name.
|
||||
if (L.checker.getConstructSignatures(L.typeOf(access.expression)).length > 0) {
|
||||
if (member === "setMaxListeners" && args.length === 1) {
|
||||
if (L.mapTypeOf(L.typeOf(args[0]!))?.kind !== "f64") {
|
||||
// Node's runtime ladder over the DOM value ("setMaxListeners" is
|
||||
// the message's slot for the static form).
|
||||
const raw = L.lowerExpr(args[0]!);
|
||||
if (raw.type.kind === "dyn" || raw.kind === "unitLit" || L.dynConvertible(raw.type)) {
|
||||
const n: IrExpr = raw.type.kind === "dyn" ? raw : { kind: "dynFrom", value: raw, type: DYN, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "emitter.setDefaultMaxChk",
|
||||
args: [n, { kind: "strLit", value: "setMaxListeners", type: STRING, loc }],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
const n = L.lowerExprExpecting(args[0]!, F64);
|
||||
if (n.type.kind !== "f64") {
|
||||
L.noLowering(
|
||||
@@ -518,6 +533,36 @@ export function lowerEmitterMethodCall(L: Lowerer, call: ts.CallExpression,
|
||||
return { kind: "libCall", fn: "emitter.setDefaultMax", args: [n], type: VOID, loc };
|
||||
}
|
||||
if (member === "setMaxListeners") {
|
||||
// The per-target form with a target that is provably NOT an
|
||||
// emitter/EventTarget (the invalid-input probes): Node validates n
|
||||
// first, then throws ERR_INVALID_ARG_TYPE on the target. Claimed
|
||||
// when n is a pure read (identifier/literal — nothing to evaluate)
|
||||
// and the target crosses into the DOM for the Received tail.
|
||||
if (args.length === 2 && !args.some(ts.isSpreadElement)) {
|
||||
const nT = L.mapTypeOf(L.typeOf(args[0]!));
|
||||
const tT = L.mapTypeOf(L.typeOf(args[1]!));
|
||||
const nPure = ts.isIdentifier(args[0]!) || ts.isLiteralExpression(args[0]!);
|
||||
const targetNotEmitter =
|
||||
tT !== null && tT.kind !== "dyn" && tT.kind !== "jsval" &&
|
||||
!(tT.kind === "object");
|
||||
if (nT?.kind === "f64" && nPure && targetNotEmitter) {
|
||||
const raw = L.lowerExpr(args[1]!);
|
||||
if (raw.type.kind === "dyn" || raw.kind === "unitLit" || L.dynConvertible(raw.type)) {
|
||||
const got: IrExpr = raw.type.kind === "dyn" ? raw : { kind: "dynFrom", value: raw, type: DYN, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "error.argTypeThrow",
|
||||
args: [
|
||||
{ kind: "strLit", value: "eventTargets", type: STRING, loc },
|
||||
{ kind: "strLit", value: "an instance of EventEmitter or EventTarget", type: STRING, loc },
|
||||
got,
|
||||
],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
L.noLowering(
|
||||
`EventEmitter.setMaxListeners with ${args.length} arguments`,
|
||||
call,
|
||||
@@ -774,6 +819,16 @@ export function lowerEmitterMethodCall(L: Lowerer, call: ts.CallExpression,
|
||||
if (member === "setMaxListeners") {
|
||||
if (args.length !== 1) L.noLowering(`setMaxListeners with ${args.length} arguments`, call);
|
||||
const receiver = L.lowerExpr(access.expression);
|
||||
if (L.mapTypeOf(L.typeOf(args[0]!))?.kind !== "f64") {
|
||||
// The invalid-input probes (string n, dyn helpers): Node's ladder
|
||||
// runs at runtime — ERR_INVALID_ARG_TYPE for non-numbers,
|
||||
// ERR_OUT_OF_RANGE below zero, and a well-typed dyn still applies.
|
||||
const raw = L.lowerExpr(args[0]!);
|
||||
if (raw.type.kind === "dyn" || raw.kind === "unitLit" || L.dynConvertible(raw.type)) {
|
||||
const n: IrExpr = raw.type.kind === "dyn" ? raw : { kind: "dynFrom", value: raw, type: DYN, loc };
|
||||
return { kind: "libCall", fn: "emitter.setMaxChk", args: [receiver, n], type: receiver.type, loc };
|
||||
}
|
||||
}
|
||||
const n = L.lowerExprExpecting(args[0]!, F64);
|
||||
return { kind: "libCall", fn: "emitter.setMax", args: [receiver, n], type: receiver.type, loc };
|
||||
}
|
||||
|
||||
@@ -6618,6 +6618,49 @@ export function lowerBinary(L: Lowerer, expr: ts.BinaryExpression): IrExpr {
|
||||
const value = L.lowerExprExpecting(expr.right, target.type);
|
||||
return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
|
||||
}
|
||||
// `events.defaultMaxListeners = v` — the module-property write
|
||||
// Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
|
||||
// the value crosses into the DOM and the runtime ladder throws
|
||||
// ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE with Node's exact slot
|
||||
// name; valid numbers apply. The expression's value is the RHS
|
||||
// (JS's assignment yield).
|
||||
if (
|
||||
op === ts.SyntaxKind.EqualsToken &&
|
||||
ts.isPropertyAccessExpression(expr.left) &&
|
||||
!expr.left.questionDotToken &&
|
||||
expr.left.name.text === "defaultMaxListeners"
|
||||
) {
|
||||
const bi = L.builtinMemberOf(expr.left);
|
||||
if (bi && bi.module === "events" && bi.member === "defaultMaxListeners") {
|
||||
const rhs = L.lowerExpr(expr.right);
|
||||
if (rhs.type.kind === "dyn" || rhs.kind === "unitLit" || L.dynConvertible(rhs.type)) {
|
||||
const valTmp = L.declareHiddenLocal("%setVal", rhs.type);
|
||||
const valRef = (): IrExpr => ({ kind: "varRef", localId: valTmp.id, type: rhs.type, loc });
|
||||
const dynVal: IrExpr =
|
||||
rhs.type.kind === "dyn" ? valRef() : { kind: "dynFrom", value: valRef(), type: DYN, loc };
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: valTmp.id, init: rhs, loc },
|
||||
{
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "emitter.setDefaultMaxChk",
|
||||
args: [dynVal, { kind: "strLit", value: "defaultMaxListeners", type: STRING, loc }],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
},
|
||||
],
|
||||
result: valRef(),
|
||||
type: rhs.type,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Member targets whose storage IS a module global — namespace
|
||||
// members (`N.x`) and expando function members (`Foo.baz`): the
|
||||
// same assignExpr, since the "member" is a variable.
|
||||
|
||||
@@ -3578,6 +3578,32 @@ function isEsModuleStamp(expr: ts.Expression): boolean {
|
||||
};
|
||||
}
|
||||
}
|
||||
// `events.defaultMaxListeners = v` in STATEMENT position — the
|
||||
// module-property write Node validates (the lowerBinary
|
||||
// expression twin): the value crosses into the DOM, the runtime
|
||||
// ladder throws Node's exact errors, valid numbers apply.
|
||||
if (!expr.left.questionDotToken && expr.left.name.text === "defaultMaxListeners") {
|
||||
const bi = L.builtinMemberOf(expr.left);
|
||||
if (bi && bi.module === "events" && bi.member === "defaultMaxListeners") {
|
||||
const loc = locOf(expr);
|
||||
const rhs = L.lowerExpr(expr.right);
|
||||
if (rhs.type.kind === "dyn" || rhs.kind === "unitLit" || L.dynConvertible(rhs.type)) {
|
||||
const dynVal: IrExpr =
|
||||
rhs.type.kind === "dyn" ? rhs : { kind: "dynFrom", value: rhs, type: DYN, loc };
|
||||
return {
|
||||
kind: "exprStmt",
|
||||
expr: {
|
||||
kind: "libCall",
|
||||
fn: "emitter.setDefaultMaxChk",
|
||||
args: [dynVal, { kind: "strLit", value: "defaultMaxListeners", type: STRING, loc }],
|
||||
type: VOID,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// `r._read = fn` / `w._write = fn` (and the other underscore
|
||||
// methods) on a runtime-stream-rooted receiver: Node's
|
||||
// own-property shadow of the prototype method — the runtime
|
||||
|
||||
@@ -7083,6 +7083,11 @@ export class Lowerer {
|
||||
}
|
||||
const bi = this.builtinMemberOf(expr);
|
||||
if (!bi) return null;
|
||||
// events.defaultMaxListeners READS the process-wide default (its
|
||||
// write twin routes through emitter.setDefaultMaxChk).
|
||||
if (bi.module === "events" && bi.member === "defaultMaxListeners") {
|
||||
return { kind: "libCall", fn: "emitter.getDefaultMax", args: [], type: F64, loc };
|
||||
}
|
||||
const c = builtinModuleConstOf(this, bi.module, bi.member);
|
||||
if (c !== undefined) return builtinConstLit(c, loc);
|
||||
if (bi.member === "constants" && bi.module === "fs") {
|
||||
|
||||
@@ -2521,6 +2521,19 @@ export type IrLibFn =
|
||||
* finite numbers coerce (negatives answer now/1000, Node's shape);
|
||||
* everything else throws Node's ERR_INVALID_ARG_TYPE. May-throw. */
|
||||
| "fs.toUnixTimestamp"
|
||||
/** The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME-
|
||||
* rendered Received tail: args [argname, "of type ..." clause, the
|
||||
* offending DOM value]. ALWAYS THROWS; polymorphic result (the
|
||||
* error.nodeThrow pattern). May-throw seed. */
|
||||
| "error.argTypeThrow"
|
||||
/** The checked-dynamic max-listeners ladders: setMaxChk is the
|
||||
* instance form over a DOM n (non-numbers ERR_INVALID_ARG_TYPE,
|
||||
* negatives/NaN ERR_OUT_OF_RANGE; +1 receiver back — chaining);
|
||||
* setDefaultMaxChk is the static/property form, its second argument
|
||||
* naming the message slot ("setMaxListeners" for the static call,
|
||||
* "defaultMaxListeners" for the module-property assignment). */
|
||||
| "emitter.setMaxChk"
|
||||
| "emitter.setDefaultMaxChk"
|
||||
/** The Buffer forms of the fs quartet: readFileSync(path) with NO
|
||||
* encoding → bytes<u8> (+1), writeFileSync(path, bytes), and the
|
||||
* fs/promises readFile(path) no-encoding form (an already-settled
|
||||
@@ -6204,6 +6217,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"bytes.compareChk",
|
||||
"buffer.newStringFail",
|
||||
"fs.toUnixTimestamp",
|
||||
"error.argTypeThrow",
|
||||
"emitter.setMaxChk",
|
||||
"emitter.setDefaultMaxChk",
|
||||
"fs.readFileSyncBytes",
|
||||
"fs.writeFileSyncBytes",
|
||||
"zlib.inflateSync",
|
||||
|
||||
@@ -672,6 +672,9 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"bytes.compareChk": { argTypes: [BYTES_U8, DYN, DYN, DYN, DYN, DYN], result: F64 },
|
||||
"buffer.newStringFail": { argTypes: [DYN], result: BYTES_U8 },
|
||||
"fs.toUnixTimestamp": { argTypes: [DYN], result: F64 },
|
||||
"error.argTypeThrow": { argTypes: [STRING, STRING, DYN], result: VOID },
|
||||
"emitter.setMaxChk": { argTypes: [null, DYN], result: VOID },
|
||||
"emitter.setDefaultMaxChk": { argTypes: [DYN, STRING], result: VOID },
|
||||
"fs.readFileSyncBytes": { argTypes: [STRING], result: BYTES_U8 },
|
||||
"fs.writeFileSyncBytes": { argTypes: [STRING, BYTES_U8], result: VOID },
|
||||
"fsp.readFileBytes": { argTypes: [STRING], result: { kind: "promise", inner: BYTES_U8 } },
|
||||
@@ -3831,7 +3834,7 @@ function validateFunction(
|
||||
// of the undefined global mapped to (never materialized).
|
||||
break;
|
||||
}
|
||||
if (e.fn === "error.nodeThrow") {
|
||||
if (e.fn === "error.nodeThrow" || e.fn === "error.argTypeThrow") {
|
||||
// Always throws — the result type is the replaced expression's
|
||||
// own (never materialized; the global.undefRead pattern).
|
||||
break;
|
||||
@@ -4050,6 +4053,7 @@ function validateFunction(
|
||||
break;
|
||||
}
|
||||
if (e.fn.startsWith("emitter.") && e.fn !== "emitter.setDefaultMax" &&
|
||||
e.fn !== "emitter.setDefaultMaxChk" &&
|
||||
e.fn !== "emitter.getDefaultMax" && e.fn !== "emitter.checkListener") {
|
||||
// Receiver: an emitter-hierarchy object (the %EventEmitter class
|
||||
// itself, or a class whose base chain reaches it). emitter.new
|
||||
@@ -4077,7 +4081,8 @@ function validateFunction(
|
||||
if (e.fn === "emitter.on" || e.fn === "emitter.off" ||
|
||||
e.fn === "emitter.onDyn" || e.fn === "emitter.offDyn" ||
|
||||
e.fn === "emitter.onData" || e.fn === "emitter.onDataDyn" ||
|
||||
e.fn === "emitter.removeAll" || e.fn === "emitter.setMax") {
|
||||
e.fn === "emitter.removeAll" || e.fn === "emitter.setMax" ||
|
||||
e.fn === "emitter.setMaxChk") {
|
||||
if (!typeEquals(e.type, e.args[0]!.type)) {
|
||||
err(`libCall ${e.fn} must return its receiver's type (the chaining 'this')`, e.loc);
|
||||
}
|
||||
|
||||
@@ -662,6 +662,18 @@ ScrArr *scr_emitter_listeners(ScrEmitter *em, ScrStr *name) {
|
||||
|
||||
/* ── max listeners ────────────────────────────────────────────────────── */
|
||||
|
||||
/* The checked-dynamic setMaxListeners ladder: non-numbers throw Node's
|
||||
* ERR_INVALID_ARG_TYPE ("The \"setMaxListeners\" argument must be of
|
||||
* type number. Received ..."), numbers run the range gate below.
|
||||
* Borrowed dyn; +1 receiver either way (the pending check unwinds). */
|
||||
ScrEmitter *scr_emitter_set_max_chk(ScrEmitter *em, const ScrDyn *n) {
|
||||
if (n->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail("setMaxListeners", "of type number", n);
|
||||
return scr_emitter_retain(em);
|
||||
}
|
||||
return scr_emitter_set_max(em, n->v.num);
|
||||
}
|
||||
|
||||
ScrEmitter *scr_emitter_set_max(ScrEmitter *em, double n) {
|
||||
if (!(n >= 0)) { /* negatives and NaN — Node's validateNumber */
|
||||
char num[32];
|
||||
@@ -682,6 +694,28 @@ double scr_emitter_get_max(ScrEmitter *em) {
|
||||
return scr_emitter_default_max;
|
||||
}
|
||||
|
||||
/* The checked-dynamic defaultMaxListeners ladder — `name` picks the
|
||||
* message's slot ("setMaxListeners" for the static call,
|
||||
* "defaultMaxListeners" for the module-property assignment, Node's own
|
||||
* split). Borrowed. */
|
||||
void scr_emitter_set_default_max_chk(const ScrDyn *n, const ScrStr *name) {
|
||||
if (n->kind != SCR_DYN_NUM) {
|
||||
scr_dyn_arg_type_fail(name->data, "of type number", n);
|
||||
return;
|
||||
}
|
||||
if (!(n->v.num >= 0)) {
|
||||
char num[32];
|
||||
size_t nlen = scr_f64_to_str(n->v.num, num);
|
||||
char msg[128];
|
||||
int len = snprintf(msg, sizeof msg,
|
||||
"The value of \"%s\" is out of range. It must be >= 0. Received %.*s",
|
||||
name->data, (int)nlen, num);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
||||
return;
|
||||
}
|
||||
scr_emitter_set_default_max(n->v.num);
|
||||
}
|
||||
|
||||
void scr_emitter_set_default_max(double n) {
|
||||
if (!(n >= 0)) { /* negatives and NaN — Node's validateNumber(n, "setMaxListeners", 0) */
|
||||
char num[32];
|
||||
|
||||
@@ -635,6 +635,14 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
|
||||
* number (5)") — the handle dispatchers' and argument validators'
|
||||
* per-arg gates. `expected` is the full "of type ..."/"an instance of
|
||||
* ..." clause. */
|
||||
/* The compiler-resolved ERR_INVALID_ARG_TYPE throw with a RUNTIME-
|
||||
* rendered Received tail (error.argTypeThrow — the always-throwing
|
||||
* lowered arms whose offending value is not a literal). Borrows all
|
||||
* three; always throws catchably. */
|
||||
void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const ScrDyn *got) {
|
||||
scr_dyn_arg_type_fail(argname->data, expected->data, got);
|
||||
}
|
||||
|
||||
void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got) {
|
||||
char detail[64];
|
||||
const char *d = scr_dyn_specific_type(got, detail, sizeof detail);
|
||||
|
||||
@@ -2968,6 +2968,9 @@ void scr_dyn_check_listener(const ScrDyn *cb, const char *argname);
|
||||
* generic ERR_INVALID_ARG_TYPE thrower over it (`expected` is the whole
|
||||
* "of type ..." clause). The handle dispatchers' per-arg gates. */
|
||||
const char *scr_dyn_specific_type(const ScrDyn *v, char *buf, size_t cap);
|
||||
/* ERR_INVALID_ARG_TYPE with the runtime-rendered Received tail (the
|
||||
* error.argTypeThrow libCall). Borrows all three; always throws. */
|
||||
void scr_throw_arg_type(const ScrStr *argname, const ScrStr *expected, const ScrDyn *got);
|
||||
void scr_dyn_arg_type_fail(const char *argname, const char *expected, const ScrDyn *got);
|
||||
/* Listener-closure builders for the handle dispatchers' .on(...) paths:
|
||||
* a runtime-built ScrClosure whose capture is the boxed dyn listener and
|
||||
@@ -4181,6 +4184,9 @@ ScrBytes *scr_buffer_new_string_fail(const ScrDyn *got);
|
||||
* numbers coerce (negatives answer now/1000), the rest throw Node's
|
||||
* ERR_INVALID_ARG_TYPE. Borrowed. */
|
||||
double scr_fs_to_unix_timestamp(const ScrDyn *t);
|
||||
/* The checked-dynamic max-listeners ladders (scr_events_emitter.c). */
|
||||
ScrEmitter *scr_emitter_set_max_chk(ScrEmitter *em, const ScrDyn *n);
|
||||
void scr_emitter_set_default_max_chk(const ScrDyn *n, const ScrStr *name);
|
||||
double scr_bytes_index_of(const ScrBytes *b, const ScrBytes *needle, double off, double align, bool fwd);
|
||||
double scr_bytes_index_of_num(const ScrBytes *b, double v, double off, bool fwd);
|
||||
ScrBytes *scr_bytes_fill(ScrBytes *b, const ScrBytes *pattern, double nargs, double offset, double end);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// The max-listeners argument ladders, Node-exact: setMaxListeners over a
|
||||
// non-number throws ERR_INVALID_ARG_TYPE with the rendered Received
|
||||
// tail, negatives/NaN throw validateNumber's ERR_OUT_OF_RANGE, the
|
||||
// module-property assignment names its own slot (defaultMaxListeners),
|
||||
// the qualified static names setMaxListeners, and valid values apply.
|
||||
'use strict';
|
||||
const events = require('events');
|
||||
const e = new events.EventEmitter();
|
||||
const show = (fn) => {
|
||||
try {
|
||||
console.log('ret', fn());
|
||||
} catch (err) {
|
||||
console.log(`${err.name}|${err.code}|${err.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
show(() => { e.setMaxListeners(NaN); });
|
||||
show(() => { e.setMaxListeners(-1); });
|
||||
show(() => { e.setMaxListeners('and even this'); });
|
||||
show(() => { e.setMaxListeners(null); });
|
||||
show(() => { e.setMaxListeners(42); });
|
||||
console.log(e.getMaxListeners());
|
||||
|
||||
show(() => { events.defaultMaxListeners = NaN; });
|
||||
show(() => { events.defaultMaxListeners = -1; });
|
||||
show(() => { events.defaultMaxListeners = 'and even this'; });
|
||||
show(() => { events.defaultMaxListeners = 20; });
|
||||
console.log(events.defaultMaxListeners);
|
||||
|
||||
show(() => { events.EventEmitter.setMaxListeners(NaN); });
|
||||
show(() => { events.EventEmitter.setMaxListeners('nope'); });
|
||||
show(() => { events.EventEmitter.setMaxListeners(11); });
|
||||
console.log(events.defaultMaxListeners);
|
||||
Reference in New Issue
Block a user