mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-02 07:43:42 +08:00
Pass console lines and byte chunks to a patched stdio write
Node's console writes each formatted line with stream.write(line, errorHandler), so a replaced process.stdout.write sees a string and a function; a Buffer write arrives as a Buffer. The stdio write hook carried only a `wide` bit, so every intercepted chunk became a JavaScript string and console lines carried a single argument. Replace that bit with a `flags` int (SCR_STDIO_HOOK_WIDE, _CONSOLE, _BYTES) and keep the hook signature at four parameters, so the console write path and the byte paths differ only in what they pass. scr_stdio_intercept now builds a chunk instead of a string, boxes SCR_STDIO_HOOK_BYTES as a Buffer, and appends a console error handler for SCR_STDIO_HOOK_CONSOLE. Text callers of the old string entry point move to a new scr_stdio_write_text. Corpus: stdout-patch-console-arguments.js. Co-Authored-By: Claude Code <noreply@anthropic.com> Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/stdout-patch-console-arguments.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -203,7 +203,7 @@ void scr_init(void) {
|
||||
* the stream differs. */
|
||||
/* A real definition lets relocatable library links localize this hook;
|
||||
* a tentative common symbol stays externally visible on Mach-O. */
|
||||
SCR_TL bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len, bool wide) = NULL;
|
||||
SCR_TL bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len, int flags) = NULL;
|
||||
SCR_TL bool (*scr_stdio_capture)(int fd, const void *data, size_t len) = NULL;
|
||||
|
||||
SCR_TL bool scr_stdin_fed = false;
|
||||
@@ -420,7 +420,7 @@ static void scr_console_write(int fd, size_t n, const ScrLogArg *args, bool glob
|
||||
}
|
||||
if (scr_stdio_write_hook) {
|
||||
/* A patched write receives the JavaScript string, lone surrogates kept. */
|
||||
scr_stdio_write_hook(fd, line, used, wide);
|
||||
scr_stdio_write_hook(fd, line, used, (wide ? SCR_STDIO_HOOK_WIDE : 0) | SCR_STDIO_HOOK_CONSOLE);
|
||||
if (scr_exc_pending()) scr_exc_clear();
|
||||
} else {
|
||||
if (wide) scr_wtf8_to_utf8(line, used);
|
||||
@@ -481,8 +481,16 @@ int scr_stdio_write_raw(int fd, const void *data, size_t len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
/* A byte chunk (process.stdout.write(buffer), a stream piped to stdout). */
|
||||
bool scr_stdio_write(int fd, const void *data, size_t len) {
|
||||
if (scr_stdio_write_hook) return scr_stdio_write_hook(fd, data, len, false);
|
||||
if (scr_stdio_write_hook) return scr_stdio_write_hook(fd, data, len, SCR_STDIO_HOOK_BYTES);
|
||||
(void)scr_stdio_write_raw(fd, data, len);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A string chunk that is plain UTF-8. */
|
||||
bool scr_stdio_write_text(int fd, const void *data, size_t len) {
|
||||
if (scr_stdio_write_hook) return scr_stdio_write_hook(fd, data, len, 0);
|
||||
(void)scr_stdio_write_raw(fd, data, len);
|
||||
return true;
|
||||
}
|
||||
@@ -490,7 +498,7 @@ bool scr_stdio_write(int fd, const void *data, size_t len) {
|
||||
/* A string chunk: a patched write receives the string itself; the stream
|
||||
* receives its UTF-8, each lone surrogate as U+FFFD. */
|
||||
static __attribute__((noinline)) bool scr_stdio_write_wide(int fd, const ScrStr *s) {
|
||||
if (scr_stdio_write_hook) return scr_stdio_write_hook(fd, s->data, s->len, true);
|
||||
if (scr_stdio_write_hook) return scr_stdio_write_hook(fd, s->data, s->len, SCR_STDIO_HOOK_WIDE);
|
||||
char *owned;
|
||||
const char *bytes = scr_str_utf8(s, &owned);
|
||||
(void)scr_stdio_write_raw(fd, bytes, s->len);
|
||||
@@ -500,5 +508,5 @@ static __attribute__((noinline)) bool scr_stdio_write_wide(int fd, const ScrStr
|
||||
|
||||
bool scr_stdio_write_str(int fd, const ScrStr *s) {
|
||||
if (__builtin_expect(scr_wtf8_any(), 0) && !s->ascii) return scr_stdio_write_wide(fd, s);
|
||||
return scr_stdio_write(fd, s->data, s->len);
|
||||
return scr_stdio_write_text(fd, s->data, s->len);
|
||||
}
|
||||
|
||||
@@ -489,8 +489,8 @@ void scr_console_clear(void) {
|
||||
if (!scr_process_is_tty(1)) return;
|
||||
const char *term = getenv("TERM");
|
||||
if (term && strcmp(term, "dumb") == 0) return;
|
||||
scr_stdio_write(1, "\x1b[1;1H", 6);
|
||||
scr_stdio_write(1, "\x1b[0J", 4);
|
||||
scr_stdio_write_text(1, "\x1b[1;1H", 6);
|
||||
scr_stdio_write_text(1, "\x1b[0J", 4);
|
||||
}
|
||||
|
||||
/* console.dir over a dyn value: inspect with custom inspection off. The
|
||||
|
||||
@@ -1507,9 +1507,19 @@ static ScrDyn *scr_stdio_invoke(void *ptr, ScrDyn *self, const char *name,
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool scr_stdio_intercept(int fd, const void *data, size_t length, bool wide) {
|
||||
/* Node's console error handler for a stream write (createWriteErrorHandler):
|
||||
* console swallows write errors, so it ignores its argument. */
|
||||
static ScrDyn *scr_stdio_console_error_handler(ScrClosure *cb, ScrDyn *const *args, size_t argc) {
|
||||
(void)cb; (void)args; (void)argc;
|
||||
return scr_dyn_retain(scr_dyn_undefined());
|
||||
}
|
||||
|
||||
static SCR_TL ScrDyn *scr_stdio_console_handler = NULL;
|
||||
|
||||
static bool scr_stdio_intercept(int fd, const void *data, size_t length, int flags) {
|
||||
ScrStdio *stream = &scr_stdio_streams[fd == 2 ? 2 : 1];
|
||||
if (!stream->write || scr_dyn_strict_eq(stream->write, scr_stdio_methods[STDIO_WRITE])) {
|
||||
bool wide = (flags & SCR_STDIO_HOOK_WIDE) != 0;
|
||||
char *owned = NULL;
|
||||
if (wide && scr_wtf8_find_surrogate(data, length)) {
|
||||
owned = malloc(length ? length : 1);
|
||||
@@ -1522,12 +1532,22 @@ static bool scr_stdio_intercept(int fd, const void *data, size_t length, bool wi
|
||||
free(owned);
|
||||
return true;
|
||||
}
|
||||
ScrStr *text = scr_str_new(data, length);
|
||||
ScrDyn *chunk = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
ScrDyn *chunk;
|
||||
if (flags & SCR_STDIO_HOOK_BYTES) {
|
||||
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)length);
|
||||
memcpy(b->data, data, length);
|
||||
chunk = scr_dyn_new_chunk(b);
|
||||
scr_bytes_release(b);
|
||||
} else {
|
||||
ScrStr *text = scr_str_new(data, length);
|
||||
chunk = scr_dyn_new_str(text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
ScrDyn *argv[2] = {chunk, scr_stdio_console_handler};
|
||||
size_t argc = (flags & SCR_STDIO_HOOK_CONSOLE) ? 2 : 1;
|
||||
scr_dyn_this_push(stream, SCR_DYNH_STDIO);
|
||||
ScrDyn *fn = scr_dyn_retain(stream->write);
|
||||
ScrDyn *result = scr_dyn_call(fn, &chunk, 1, "write");
|
||||
ScrDyn *result = scr_dyn_call(fn, argv, argc, "write");
|
||||
scr_dyn_release(fn);
|
||||
scr_dyn_this_pop();
|
||||
bool accepted = result && scr_dyn_truthy(result);
|
||||
@@ -1562,6 +1582,8 @@ static void scr_stdio_cleanup(void) {
|
||||
scr_dyn_release(scr_stdio_methods[i]);
|
||||
scr_stdio_methods[i] = NULL;
|
||||
}
|
||||
scr_dyn_release(scr_stdio_console_handler);
|
||||
scr_stdio_console_handler = NULL;
|
||||
scr_stdio_initialized = false;
|
||||
}
|
||||
|
||||
@@ -1582,6 +1604,7 @@ ScrDyn *scr_process_stdio(double fd) {
|
||||
scr_box_set_f64(cb->caps[0], i);
|
||||
scr_stdio_methods[i] = scr_dyn_new_func(cb, scr_stdio_method, i == STDIO_WRITE ? 3 : 1, "", scr_stdio_names[i]);
|
||||
}
|
||||
scr_stdio_console_handler = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_stdio_console_error_handler, 1, "", "");
|
||||
scr_stdio_write_hook = scr_stdio_intercept;
|
||||
}
|
||||
return scr_dyn_new_handle(&scr_stdio_streams[fd == 0 ? 0 : fd == 2 ? 2 : 1], SCR_DYNH_STDIO);
|
||||
|
||||
@@ -71,9 +71,16 @@ ScrStr *scr_process_cwd(void); /* +1 fresh (getcwd) */
|
||||
bool scr_stdio_write(int fd, const void *data, size_t len);
|
||||
bool scr_stdio_write_str(int fd, const ScrStr *s);
|
||||
int scr_stdio_write_raw(int fd, const void *data, size_t len);
|
||||
/* `wide` marks string bytes that may hold lone surrogates, which become
|
||||
* U+FFFD when they reach the stream itself. */
|
||||
extern SCR_TL bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len, bool wide);
|
||||
/* `flags` describes the chunk a patched write receives: SCR_STDIO_HOOK_WIDE
|
||||
* marks string bytes that may hold lone surrogates (they become U+FFFD when
|
||||
* they reach the stream itself), SCR_STDIO_HOOK_CONSOLE a console line
|
||||
* (Node's console passes its error handler as the second argument), and
|
||||
* SCR_STDIO_HOOK_BYTES a byte chunk (the replacement receives a Buffer). */
|
||||
enum { SCR_STDIO_HOOK_WIDE = 1, SCR_STDIO_HOOK_CONSOLE = 2, SCR_STDIO_HOOK_BYTES = 4 };
|
||||
extern SCR_TL bool (*scr_stdio_write_hook)(int fd, const void *data, size_t len, int flags);
|
||||
/* A string chunk given as bytes (UTF-8, no lone surrogates): what
|
||||
* scr_stdio_write was for callers that write text. */
|
||||
bool scr_stdio_write_text(int fd, const void *data, size_t len);
|
||||
/* A worker created with the stdout or stderr option: answers true when it
|
||||
* took the bytes for fd 1 or 2 instead of the process descriptor. */
|
||||
extern SCR_TL bool (*scr_stdio_capture)(int fd, const void *data, size_t len);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// What a replaced process.stdout.write receives from console: Node's
|
||||
// console writes each formatted line with stream.write(line, errorHandler),
|
||||
// so the replacement sees a string and a function as its second argument
|
||||
// for console.log, info, debug, dir, count, table and grouped lines alike;
|
||||
// a direct write passes exactly its own arguments. The error handler accepts
|
||||
// a null error.
|
||||
const original = process.stdout.write;
|
||||
const seen = [];
|
||||
process.stdout.write = function (chunk, encodingOrCallback, callback) {
|
||||
const kind = typeof chunk === "string" ? "string" : Buffer.isBuffer(chunk) ? "Buffer" : chunk.constructor.name;
|
||||
seen.push(`${kind}:${JSON.stringify(String(chunk))}:${typeof encodingOrCallback}:${typeof callback}`);
|
||||
if (typeof encodingOrCallback === "function") encodingOrCallback(null);
|
||||
return true;
|
||||
};
|
||||
console.log("line", 1);
|
||||
console.info("info");
|
||||
console.debug("debug");
|
||||
console.log("%s=%d", "x", 2);
|
||||
console.dir({ a: 1 });
|
||||
console.count();
|
||||
console.group("group");
|
||||
console.log("inside");
|
||||
console.groupEnd();
|
||||
console.table([{ a: 1 }]);
|
||||
process.stdout.write("direct");
|
||||
process.stdout.write("hex", "utf8");
|
||||
process.stdout.write(Buffer.from("bytes"));
|
||||
process.stdout.write = original;
|
||||
console.log(seen.join("\n"));
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5460, "san": 7830 }
|
||||
Reference in New Issue
Block a user