mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Share defaultMaxListeners with the typed EventEmitter, link instanceof alone, and clean up a pipe once
- EventEmitter.defaultMaxListeners on the untyped constructor is Node's
accessor over the default the typed EventEmitter reads: assignments
validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and
getMaxListeners() of emitters without their own limit answers the new
value.
- dyn.builtinInstanceOf and dyn.functionInstanceOf link the
constructor-function unit and the units it calls (dyn-invoke, inspect,
symbol, emitter), so a program whose only use is `this instanceof F`
links.
- The legacy pipe's cleanup runs once: when the destination emits
'close' from end(), the cleanup still queued on the source's 'end'
finds the listeners removed and emit('end') answers true.
This commit is contained in:
@@ -236,7 +236,12 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
"scr_util_callable.c",
|
||||
),
|
||||
unit(
|
||||
(u) => u.opts.dynBuiltinClass && u.opts.inspect && u.opts.dynInvoke && u.opts.symbol,
|
||||
(u) =>
|
||||
u.opts.dynBuiltinClass &&
|
||||
u.opts.inspect &&
|
||||
u.opts.dynInvoke &&
|
||||
u.opts.symbol &&
|
||||
(u.opts.emitter || u.opts.workers),
|
||||
"scr_dyn_builtin_class.c",
|
||||
),
|
||||
unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"),
|
||||
|
||||
@@ -234,10 +234,21 @@ const CHILD_PROCESS_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
/** libCall name prefixes that link the child-process unit. */
|
||||
const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRes.", "writer."];
|
||||
|
||||
/** The entry points scr_dyn_builtin_class.c defines. The unit calls
|
||||
* listeners through the dyn-invoke unit, renders unhandled 'error'
|
||||
* payloads through the inspect unit, creates EventEmitter's private
|
||||
* symbols through the symbol unit and shares defaultMaxListeners with the
|
||||
* emitter unit, so each of them links all five. */
|
||||
const BUILTIN_CLASS_LIB_FNS: readonly IrLibFn[] = [
|
||||
"dyn.builtinClass",
|
||||
"dyn.builtinInstanceOf",
|
||||
"dyn.functionInstanceOf",
|
||||
];
|
||||
|
||||
/** Entry points that link the symbol unit, besides the sym. and disposable. families. */
|
||||
const SYMBOL_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
// EventEmitter's kShapeMode and kCapture properties
|
||||
"dyn.builtinClass",
|
||||
...BUILTIN_CLASS_LIB_FNS,
|
||||
"dyn.usingDispose",
|
||||
"dyn.usingMethod",
|
||||
"util.isDeepStrictEqual",
|
||||
@@ -511,11 +522,11 @@ const RUNTIME_FEATURE_RULES = {
|
||||
* unit, render unhandled 'error' payloads through the inspect unit and
|
||||
* create EventEmitter's private symbols through the symbol unit. */
|
||||
dynBuiltinClass: rule({
|
||||
fns: ["dyn.builtinClass", "dyn.builtinInstanceOf", "dyn.functionInstanceOf"],
|
||||
fns: [...BUILTIN_CLASS_LIB_FNS],
|
||||
}),
|
||||
dynInvoke: rule({
|
||||
initial: (mod) => mod.workers === true,
|
||||
fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, "dyn.builtinClass"],
|
||||
fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS],
|
||||
// Constant-key reads of Object.prototype member names answer the
|
||||
// inherited member through scr_dyn_invoke.c.
|
||||
expr: (node, _tables) =>
|
||||
@@ -532,7 +543,7 @@ const RUNTIME_FEATURE_RULES = {
|
||||
emitter: rule({
|
||||
initial: (mod) =>
|
||||
mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS),
|
||||
fns: [...STREAM_LIB_FNS],
|
||||
fns: [...STREAM_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS],
|
||||
prefixes: ["emitter.", ...STREAM_LIB_PREFIXES],
|
||||
}),
|
||||
/** Subclasses of the runtime EventTarget/Event bases tear down and box
|
||||
@@ -567,7 +578,7 @@ const RUNTIME_FEATURE_RULES = {
|
||||
"global.native",
|
||||
"util.styleText",
|
||||
"crypto.cipherNew",
|
||||
"dyn.builtinClass",
|
||||
...BUILTIN_CLASS_LIB_FNS,
|
||||
...UTIL_CALLABLE_LIB_FNS,
|
||||
],
|
||||
prefixes: ["insp.", "console.", "test."],
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5817-constructor-function-instanceof.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5818-constructor-function-emitter-defaults.cjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
// Untyped EventEmitter and stream constructor values call listeners
|
||||
// through the dyn-invoke unit, inspect unhandled 'error' payloads and
|
||||
// create EventEmitter's private symbols.
|
||||
["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol")],
|
||||
["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol", "emitter")],
|
||||
["scr_stream.c", "stream"],
|
||||
// The zlib stream classes reference both the zlib engine and the stream unit.
|
||||
["scr_zlib_stream.c", all("zlib", "stream")],
|
||||
|
||||
@@ -263,9 +263,7 @@ static double bc_max_listeners(ScrDyn *self) {
|
||||
if (!max) return -1;
|
||||
double value;
|
||||
if (bc_undefined(max)) {
|
||||
ScrDyn *fallback = bc_get(bc_class(BC_EMITTER), "defaultMaxListeners");
|
||||
value = fallback ? bc_number(fallback) : 10;
|
||||
if (fallback) scr_dyn_release(fallback);
|
||||
value = scr_emitter_get_default_max();
|
||||
} else {
|
||||
value = bc_number(max);
|
||||
}
|
||||
@@ -1017,6 +1015,42 @@ static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc)
|
||||
return self;
|
||||
}
|
||||
|
||||
/* EventEmitter.defaultMaxListeners, Node's accessor over the one default
|
||||
* the typed EventEmitter reads too (scr_events_emitter.c). The setter
|
||||
* validates as Node's validateNumber(arg, 'defaultMaxListeners', 0). */
|
||||
static ScrDyn *bc_default_max_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
(void)args;
|
||||
(void)argc;
|
||||
return scr_dyn_new_num(scr_emitter_get_default_max());
|
||||
}
|
||||
|
||||
static ScrDyn *bc_default_max_set(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
static const char name[] = "defaultMaxListeners";
|
||||
ScrStr *key = scr_str_new(name, sizeof name - 1);
|
||||
scr_emitter_set_default_max_chk(argc ? args[0] : scr_dyn_undefined(), key);
|
||||
scr_str_release(key);
|
||||
if (scr_exc_pending()) return NULL;
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static void bc_define_default_max(ScrDyn *ctor) {
|
||||
ScrDyn *descriptor = scr_dyn_new_obj();
|
||||
scr_dyn_obj_set(descriptor, "get", 3,
|
||||
scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_get, 0, "native:events", "get"));
|
||||
scr_dyn_obj_set(descriptor, "set", 3,
|
||||
scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_set, 1, "native:events", "set"));
|
||||
scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(true));
|
||||
ScrStr *name = scr_str_new("defaultMaxListeners", 19);
|
||||
ScrDyn *key = scr_dyn_new_str(name);
|
||||
scr_str_release(name);
|
||||
ScrDyn *defined = scr_dyn_define_property(ctor, key, descriptor);
|
||||
if (defined) scr_dyn_release(defined);
|
||||
scr_dyn_release(key);
|
||||
scr_dyn_release(descriptor);
|
||||
}
|
||||
|
||||
static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
|
||||
(void)closure;
|
||||
(void)args;
|
||||
@@ -1070,6 +1104,13 @@ static bool bc_pipe_on(ScrDyn *emitter, const char *event, ScrClosure *state, in
|
||||
}
|
||||
|
||||
static bool bc_pipe_cleanup(ScrClosure *state) {
|
||||
/* A second run (dest emitting 'close' from end(), with cleanup still
|
||||
* queued on the source's 'end') finds the listeners removed, as Node's
|
||||
* repeated removeListener calls do. */
|
||||
ScrDyn *handlers = bc_cap(state, BC_PIPE_HANDLERS);
|
||||
bool cleaned = handlers->kind == SCR_DYN_ARR && scr_dyn_arr_len(handlers) == 0;
|
||||
scr_dyn_release(handlers);
|
||||
if (cleaned) return true;
|
||||
ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE);
|
||||
ScrDyn *dest = bc_cap(state, BC_PIPE_DEST);
|
||||
bool ok = bc_pipe_remove(source, "data", state, BC_ONDATA) &&
|
||||
@@ -1367,7 +1408,7 @@ static ScrDyn *bc_new_class(int kind, ScrDyn *parent) {
|
||||
bc_method(prototype, "listenerCount", bc_listener_count, 2);
|
||||
bc_method(prototype, "eventNames", bc_event_names, 0);
|
||||
bc_set(ctor, "EventEmitter", scr_dyn_retain(ctor));
|
||||
bc_set(ctor, "defaultMaxListeners", scr_dyn_new_num(10));
|
||||
bc_define_default_max(ctor);
|
||||
ScrDyn *count = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_static_listener_count, 2, "native:events", "listenerCount");
|
||||
bc_set(ctor, "listenerCount", count);
|
||||
} else if (kind == BC_STREAM) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// `this instanceof F` in a constructor function, in a program that uses no
|
||||
// EventEmitter or stream constructor value: the call-without-new idiom
|
||||
// returns a new instance, and instanceof follows the function's prototype.
|
||||
"use strict";
|
||||
function Glob(pattern) {
|
||||
if (!(this instanceof Glob)) return new Glob(pattern);
|
||||
this.pattern = pattern;
|
||||
}
|
||||
Glob.prototype.describe = function () {
|
||||
return `glob ${this.pattern}`;
|
||||
};
|
||||
const called = Glob("*.ts");
|
||||
const constructed = new Glob("*.js");
|
||||
console.log(called.describe(), constructed.describe());
|
||||
console.log(called instanceof Glob, {} instanceof Glob, Object.create(Glob.prototype) instanceof Glob);
|
||||
@@ -0,0 +1,53 @@
|
||||
// EventEmitter.defaultMaxListeners read and written through the untyped
|
||||
// EventEmitter constructor (Node's accessor and its validation), and a
|
||||
// legacy pipe whose destination emits 'close' synchronously from end().
|
||||
"use strict";
|
||||
const util = require("util");
|
||||
const EventEmitter = require("events");
|
||||
const Stream = require("stream");
|
||||
|
||||
function Bus() {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
util.inherits(Bus, EventEmitter);
|
||||
|
||||
console.log(EventEmitter.defaultMaxListeners);
|
||||
console.log(new Bus().getMaxListeners());
|
||||
EventEmitter.defaultMaxListeners = 3;
|
||||
console.log(EventEmitter.defaultMaxListeners, new Bus().getMaxListeners());
|
||||
for (const value of [-1, "4", Number.NaN]) {
|
||||
try {
|
||||
EventEmitter.defaultMaxListeners = value;
|
||||
} catch (error) {
|
||||
console.log(error.name, error.code, error.message);
|
||||
}
|
||||
}
|
||||
console.log(EventEmitter.defaultMaxListeners);
|
||||
EventEmitter.defaultMaxListeners = 10;
|
||||
|
||||
function Source() {
|
||||
Stream.call(this);
|
||||
this.readable = true;
|
||||
}
|
||||
util.inherits(Source, Stream);
|
||||
|
||||
function Sink() {
|
||||
Stream.call(this);
|
||||
this.writable = true;
|
||||
}
|
||||
util.inherits(Sink, Stream);
|
||||
Sink.prototype.write = function (chunk) {
|
||||
console.log("write", chunk);
|
||||
return true;
|
||||
};
|
||||
Sink.prototype.end = function () {
|
||||
console.log("sink end");
|
||||
this.emit("close");
|
||||
};
|
||||
|
||||
const source = new Source();
|
||||
const sink = new Sink();
|
||||
source.pipe(sink);
|
||||
source.emit("data", "a");
|
||||
console.log("emit end", source.emit("end"));
|
||||
console.log("listeners", source.listenerCount("end"), source.listenerCount("data"), sink.listenerCount("close"));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2655, "san": 7944 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 2746, "san": 51663 }
|
||||
Reference in New Issue
Block a user