From c2a5451e93bae70d25012bba3aeac51f0756df63 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 7 Jul 2024 20:18:07 -0700 Subject: [PATCH] Fix argument validation with callbacks in node:fs (#12412) --- src/bun.js/event_loop.zig | 24 +- src/bun.js/node/node_fs_stat_watcher.zig | 4 +- src/bun.js/node/types.zig | 15 +- src/js/node/fs.promises.ts | 6 + src/js/node/fs.ts | 476 ++++++++++++++++++----- src/tagged_pointer.zig | 4 + test/js/node/fs/cp.test.ts | 2 +- 7 files changed, 424 insertions(+), 107 deletions(-) diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index af0fab9365dc38..f7db8d6a477a52 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -319,8 +319,11 @@ pub const JSCScheduler = struct { export fn Bun__queueJSCDeferredWorkTaskConcurrently(jsc_vm: *VirtualMachine, task: *JSCScheduler.JSCDeferredWorkTask) void { JSC.markBinding(@src()); var loop = jsc_vm.eventLoop(); - var concurrent_task = bun.default_allocator.create(ConcurrentTask) catch bun.outOfMemory(); - loop.enqueueTaskConcurrent(concurrent_task.from(task, .auto_deinit)); + loop.enqueueTaskConcurrent(ConcurrentTask.new(.{ + .task = Task.init(task), + .next = null, + .auto_delete = true, + })); } comptime { @@ -1256,8 +1259,7 @@ pub const EventLoop = struct { _ = this.tickConcurrentWithCount(); } - pub fn tickConcurrentWithCount(this: *EventLoop) usize { - JSC.markBinding(@src()); + fn updateCounts(this: *EventLoop) void { const delta = this.concurrent_ref.swap(0, .monotonic); const loop = this.virtual_machine.event_loop_handle.?; if (comptime Environment.isWindows) { @@ -1275,6 +1277,10 @@ pub const EventLoop = struct { loop.active -= @intCast(-delta); } } + } + + pub fn tickConcurrentWithCount(this: *EventLoop) usize { + this.updateCounts(); var concurrent = this.concurrent_tasks.popBatch(); const count = concurrent.count; @@ -1575,25 +1581,31 @@ pub const EventLoop = struct { } } pub fn enqueueTaskConcurrent(this: *EventLoop, task: *ConcurrentTask) void { - JSC.markBinding(@src()); if (comptime Environment.allow_assert) { if (this.virtual_machine.has_terminated) { @panic("EventLoop.enqueueTaskConcurrent: VM has terminated"); } } + if (comptime Environment.isDebug) { + log("enqueueTaskConcurrent({s})", .{task.task.typeName() orelse "[unknown]"}); + } + this.concurrent_tasks.push(task); this.wakeup(); } pub fn enqueueTaskConcurrentBatch(this: *EventLoop, batch: ConcurrentTask.Queue.Batch) void { - JSC.markBinding(@src()); if (comptime Environment.allow_assert) { if (this.virtual_machine.has_terminated) { @panic("EventLoop.enqueueTaskConcurrent: VM has terminated"); } } + if (comptime Environment.isDebug) { + log("enqueueTaskConcurrentBatch({d})", .{batch.count}); + } + this.concurrent_tasks.pushBatch(batch.front.?, batch.last.?, batch.count); this.wakeup(); } diff --git a/src/bun.js/node/node_fs_stat_watcher.zig b/src/bun.js/node/node_fs_stat_watcher.zig index ca41a378a038f8..d5d6c2362513f2 100644 --- a/src/bun.js/node/node_fs_stat_watcher.zig +++ b/src/bun.js/node/node_fs_stat_watcher.zig @@ -26,9 +26,9 @@ const log = bun.Output.scoped(.StatWatcher, false); fn statToJSStats(globalThis: *JSC.JSGlobalObject, stats: bun.Stat, bigint: bool) JSC.JSValue { if (bigint) { - return bun.new(StatsBig, StatsBig.init(stats)).toJS(globalThis); + return StatsBig.new(StatsBig.init(stats)).toJS(globalThis); } else { - return bun.new(StatsSmall, StatsSmall.init(stats)).toJS(globalThis); + return StatsSmall.new(StatsSmall.init(stats)).toJS(globalThis); } } diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 8f6f3a93bda82a..50220361cfb524 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -1386,6 +1386,7 @@ pub fn StatType(comptime Big: bool) type { return extern struct { pub usingnamespace if (Big) JSC.Codegen.JSBigIntStats else JSC.Codegen.JSStats; + pub usingnamespace bun.New(@This()); // Stats stores these as i32, but BigIntStats stores all of these as i64 // On windows, these two need to be u64 as the numbers are often very large. @@ -1555,7 +1556,7 @@ pub fn StatType(comptime Big: bool) type { // TODO: BigIntStats includes a `_checkModeProperty` but I dont think anyone actually uses it. pub fn finalize(this: *This) callconv(.C) void { - bun.destroy(this); + this.destroy(); } pub fn init(stat_: bun.Stat) This { @@ -1588,12 +1589,6 @@ pub fn StatType(comptime Big: bool) type { }; } - pub fn initWithAllocator(allocator: std.mem.Allocator, stat: bun.Stat) *This { - const this = allocator.create(This) catch bun.outOfMemory(); - this.* = init(stat); - return this; - } - pub fn constructor(globalObject: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame) callconv(JSC.conv) ?*This { if (Big) { globalObject.throwInvalidArguments("BigIntStats is not a constructor", .{}); @@ -1608,7 +1603,7 @@ pub fn StatType(comptime Big: bool) type { const ctime_ms: f64 = if (args.len > 12 and args[12].isNumber()) args[12].asNumber() else 0; const birthtime_ms: f64 = if (args.len > 13 and args[13].isNumber()) args[13].asNumber() else 0; - const this = bun.new(This, .{ + const this = This.new(.{ .dev = if (args.len > 0 and args[0].isNumber()) @intCast(args[0].toInt32()) else 0, .mode = if (args.len > 1 and args[1].isNumber()) args[1].toInt32() else 0, .nlink = if (args.len > 2 and args[2].isNumber()) args[2].toInt32() else 0, @@ -1658,8 +1653,8 @@ pub const Stats = union(enum) { pub fn toJSNewlyCreated(this: *const Stats, globalObject: *JSC.JSGlobalObject) JSC.JSValue { return switch (this.*) { - .big => bun.new(StatsBig, this.big).toJS(globalObject), - .small => bun.new(StatsSmall, this.small).toJS(globalObject), + .big => StatsBig.new(this.big).toJS(globalObject), + .small => StatsSmall.new(this.small).toJS(globalObject), }; } diff --git a/src/js/node/fs.promises.ts b/src/js/node/fs.promises.ts index c6951eb4172e73..ada7640b74520f 100644 --- a/src/js/node/fs.promises.ts +++ b/src/js/node/fs.promises.ts @@ -276,6 +276,12 @@ export default exports; return this[kFd]; } + [kCloseResolve]; + [kFd]; + [kFlag]; + [kClosePromise]; + [kRefs]; + async appendFile(data, options: object | string | undefined) { const fd = this[kFd]; throwEBADFIfNecessary(writeFile, fd); diff --git a/src/js/node/fs.ts b/src/js/node/fs.ts index fa1e3025fafc2f..fd279907ae205a 100644 --- a/src/js/node/fs.ts +++ b/src/js/node/fs.ts @@ -23,7 +23,7 @@ var _fs = Symbol.for("#fs"); function ensureCallback(callback) { if (!$isCallable(callback)) { - const err = new TypeError("Callback must be a function"); + const err = new TypeError('The "cb" argument must be of type function. Received ' + typeof callback); err.code = "ERR_INVALID_ARG_TYPE"; throw err; } @@ -31,6 +31,17 @@ function ensureCallback(callback) { return callback; } +// Micro-optimization: avoid creating a new function for every call +// bind() is slightly more optimized in JSC +// This code is equivalent to: +// +// function () { callback(null); } +// +function nullcallback(callback) { + return FunctionPrototypeBind.$call(callback, undefined, null); +} +const FunctionPrototypeBind = nullcallback.bind; + class FSWatcher extends EventEmitter { #watcher; #listener; @@ -137,11 +148,25 @@ class StatWatcher extends EventEmitter { } } -var access = function access(...args) { - callbackify(fs.access, args); +var access = function access(path, mode, callback) { + if ($isCallable(mode)) { + callback = mode; + mode = undefined; + } + + ensureCallback(callback); + + fs.access(path, mode).then(nullcallback(callback), callback); }, - appendFile = function appendFile(...args) { - callbackify(fs.appendFile, args); + appendFile = function appendFile(path, data, options, callback) { + if (!$isCallable(callback)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.appendFile(path, data, options).then(nullcallback(callback), callback); }, close = function close(fd, callback) { if ($isCallable(callback)) { @@ -154,22 +179,36 @@ var access = function access(...args) { throw err; } }, - rm = function rm(...args) { - callbackify(fs.rm, args); + rm = function rm(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + fs.rm(path, options).then(nullcallback(callback), callback); }, - rmdir = function rmdir(...args) { - callbackify(fs.rmdir, args); + rmdir = function rmdir(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + fs.rmdir(path, options).then(nullcallback(callback), callback); }, - copyFile = function copyFile(...args) { - const callback = ensureCallback(args[args.length - 1]); - fs.copyFile(...args).then(result => callback(null, result), callback); + copyFile = function copyFile(src, dest, mode, callback) { + if ($isCallable(mode)) { + callback = mode; + mode = 0; + } + + ensureCallback(callback); + + fs.copyFile(src, dest, mode).then(nullcallback(callback), callback); }, exists = function exists(path, callback) { - if (typeof callback !== "function") { - const err = new TypeError("Callback must be a function"); - err.code = "ERR_INVALID_ARG_TYPE"; - throw err; - } + ensureCallback(callback); + try { fs.exists.$apply(fs, [path]).then( existed => callback(existed), @@ -179,50 +218,111 @@ var access = function access(...args) { callback(false); } }, - chown = function chown(...args) { - callbackify(fs.chown, args); + chown = function chown(path, uid, gid, callback) { + ensureCallback(callback); + + fs.chown(path, uid, gid).then(nullcallback(callback), callback); }, - chmod = function chmod(...args) { - callbackify(fs.chmod, args); + chmod = function chmod(path, mode, callback) { + ensureCallback(callback); + + fs.chmod(path, mode).then(nullcallback(callback), callback); }, - fchmod = function fchmod(...args) { - callbackify(fs.fchmod, args); + fchmod = function fchmod(fd, mode, callback) { + ensureCallback(callback); + + fs.fchmod(fd, mode).then(nullcallback(callback), callback); }, - fchown = function fchown(...args) { - callbackify(fs.fchown, args); + fchown = function fchown(fd, uid, gid, callback) { + ensureCallback(callback); + + fs.fchown(fd, uid, gid).then(nullcallback(callback), callback); }, - fstat = function fstat(...args) { - callbackify(fs.fstat, args); + fstat = function fstat(fd, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + fs.fstat(fd, options).then(function (stats) { + callback(null, stats); + }, callback); }, - fsync = function fsync(...args) { - callbackify(fs.fsync, args); + fsync = function fsync(fd, callback) { + ensureCallback(callback); + + fs.fsync(fd).then(nullcallback(callback), callback); }, - ftruncate = function ftruncate(...args) { - callbackify(fs.ftruncate, args); + ftruncate = function ftruncate(fd, len, callback) { + if ($isCallable(len)) { + callback = len; + len = undefined; + } + + ensureCallback(callback); + + fs.ftruncate(fd, len).then(nullcallback(callback), callback); }, - futimes = function futimes(...args) { - callbackify(fs.futimes, args); + futimes = function futimes(fd, atime, mtime, callback) { + ensureCallback(callback); + + fs.futimes(fd, atime, mtime).then(nullcallback(callback), callback); }, - lchmod = function lchmod(...args) { - callbackify(fs.lchmod, args); + lchmod = function lchmod(path, mode, callback) { + ensureCallback(callback); + + fs.lchmod(path, mode).then(nullcallback(callback), callback); }, - lchown = function lchown(...args) { - callbackify(fs.lchown, args); + lchown = function lchown(path, uid, gid, callback) { + ensureCallback(callback); + + fs.lchown(path, uid, gid).then(nullcallback(callback), callback); }, - link = function link(...args) { - callbackify(fs.link, args); + link = function link(existingPath, newPath, callback) { + ensureCallback(callback); + + fs.link(existingPath, newPath).then(nullcallback(callback), callback); }, - mkdir = function mkdir(...args) { - callbackify(fs.mkdir, args); + mkdir = function mkdir(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.mkdir(path, options).then(nullcallback(callback), callback); }, - mkdtemp = function mkdtemp(...args) { - callbackify(fs.mkdtemp, args); + mkdtemp = function mkdtemp(prefix, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.mkdtemp(prefix, options).then(function (folder) { + callback(null, folder); + }, callback); }, - open = function open(...args) { - callbackify(fs.open, args); + open = function open(path, flags, mode, callback) { + if (arguments.length < 3) { + callback = flags; + } else if ($isCallable(mode)) { + callback = mode; + mode = undefined; + } + + ensureCallback(callback); + + fs.open(path, flags, mode).then(function (fd) { + callback(null, fd); + }, callback); }, - fdatasync = function fdatasync(...args) { - callbackify(fs.fdatasync, args); + fdatasync = function fdatasync(fd, callback) { + ensureCallback(callback); + + fs.fdatasync(fd).then(nullcallback(callback), callback); }, read = function read(fd, buffer, offsetOrOptions, length, position, callback) { let offset = offsetOrOptions; @@ -255,60 +355,151 @@ var access = function access(...args) { err => callback(err), ); }, - write = function write(...args) { - const callback = ensureCallback(args[args.length - 1]); - const promise = fs.write(...args.slice(0, -1)); - const bufferOrString = args[1]; + write = function write(fd, buffer, offsetOrOptions, length, position, callback) { + function wrapper(bytesWritten) { + callback(null, bytesWritten, buffer); + } - promise.then( - bytesWritten => callback(null, bytesWritten, bufferOrString), - err => callback(err), - ); + if ($isTypedArrayView(buffer)) { + callback ||= position || length || offsetOrOptions; + ensureCallback(callback); + + fs.write(fd, buffer, offsetOrOptions, length, position).then(wrapper, callback); + return; + } + + if (!$isCallable(position)) { + if ($isCallable(offsetOrOptions)) { + position = offsetOrOptions; + offsetOrOptions = undefined; + } else { + position = length; + } + length = "utf8"; + } + + callback = position; + ensureCallback(callback); + + fs.write(fd, buffer, offsetOrOptions, length).then(wrapper, callback); }, - readdir = function readdir(...args) { - const callback = ensureCallback(args[args.length - 1]); + readdir = function readdir(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); - fs.readdir(...args.slice(0, -1)).then(result => callback(null, result), callback); + fs.readdir(path, options).then(function (files) { + callback(null, files); + }, callback); }, - readFile = function readFile(...args) { - const callback = ensureCallback(args[args.length - 1]); - fs.readFile(...args.slice(0, -1)).then(result => callback(null, result), callback); + readFile = function readFile(path, options, callback) { + callback ||= options; + ensureCallback(callback); + + fs.readFile(path, options).then(function (data) { + callback(null, data); + }, callback); }, - writeFile = function writeFile(...args) { - callbackify(fs.writeFile, args); + writeFile = function writeFile(path, data, options, callback) { + callback ||= options; + ensureCallback(callback); + + fs.writeFile(path, data, options).then(nullcallback(callback), callback); }, - readlink = function readlink(...args) { - callbackify(fs.readlink, args); + readlink = function readlink(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.readlink(path, options).then(function (linkString) { + callback(null, linkString); + }, callback); }, - realpath = function realpath(...args) { - const callback = ensureCallback(args[args.length - 1]); - fs.realpath(...args.slice(0, -1)).then(result => callback(null, result), callback); + realpath = function realpath(p, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.realpath(p, options).then(function (resolvedPath) { + callback(null, resolvedPath); + }, callback); }, - rename = function rename(...args) { - callbackify(fs.rename, args); + rename = function rename(oldPath, newPath, callback) { + ensureCallback(callback); + + fs.rename(oldPath, newPath).then(nullcallback(callback), callback); }, - lstat = function lstat(...args) { - const callback = ensureCallback(args[args.length - 1]); - fs.lstat(...args.slice(0, -1)).then(result => callback(null, result), callback); + lstat = function lstat(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.lstat(path, options).then(function (stats) { + callback(null, stats); + }, callback); }, - stat = function stat(...args) { - const callback = ensureCallback(args[args.length - 1]); - fs.stat(...args.slice(0, -1)).then(result => callback(null, result), callback); + stat = function stat(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + fs.stat(path, options).then(function (stats) { + callback(null, stats); + }, callback); }, - symlink = function symlink(...args) { - callbackify(fs.symlink, args); + symlink = function symlink(target, path, type, callback) { + if (callback === undefined) { + callback = type; + ensureCallback(callback); + type = undefined; + } + + fs.symlink(target, path, type).then(callback, callback); }, - truncate = function truncate(...args) { - callbackify(fs.truncate, args); + truncate = function truncate(path, len, callback) { + if (typeof path === "number") { + // Apparently, node supports this + ftruncate(path, len, callback); + return; + } + + if ($isCallable(len)) { + callback = len; + len = undefined; + } + + ensureCallback(callback); + fs.truncate(path, len).then(nullcallback(callback), callback); }, - unlink = function unlink(...args) { - callbackify(fs.unlink, args); + unlink = function unlink(path, callback) { + ensureCallback(callback); + + fs.unlink(path).then(nullcallback(callback), callback); }, - utimes = function utimes(...args) { - callbackify(fs.utimes, args); + utimes = function utimes(path, atime, mtime, callback) { + ensureCallback(callback); + + fs.utimes(path, atime, mtime).then(nullcallback(callback), callback); }, - lutimes = function lutimes(...args) { - callbackify(fs.lutimes, args); + lutimes = function lutimes(path, atime, mtime, callback) { + ensureCallback(callback); + + fs.lutimes(path, atime, mtime).then(nullcallback(callback), callback); }, accessSync = fs.accessSync.bind(fs), appendFileSync = fs.appendFileSync.bind(fs), @@ -386,8 +577,17 @@ var access = function access(...args) { watch = function watch(path, options, listener) { return new FSWatcher(path, options, listener); }, - opendir = function opendir(...args) { - callbackify(promises.opendir, args); + opendir = function opendir(path, options, callback) { + if ($isCallable(options)) { + callback = options; + options = undefined; + } + + ensureCallback(callback); + + promises.opendir(path, options).then(function (dir) { + callback(null, dir); + }, callback); }; // TODO: make symbols a separate export somewhere @@ -1324,3 +1524,103 @@ export default { // return getLazyReadStream(); // }, }; + +// Preserve the names +function setName(fn, value) { + Object.$defineProperty(fn, "name", { value, enumerable: false, configurable: true }); +} +setName(Dirent, "Dirent"); +setName(FSWatcher, "FSWatcher"); +setName(ReadStream, "ReadStream"); +setName(Stats, "Stats"); +setName(WriteStream, "WriteStream"); +setName(_toUnixTimestamp, "_toUnixTimestamp"); +setName(access, "access"); +setName(accessSync, "accessSync"); +setName(appendFile, "appendFile"); +setName(appendFileSync, "appendFileSync"); +setName(chmod, "chmod"); +setName(chmodSync, "chmodSync"); +setName(chown, "chown"); +setName(chownSync, "chownSync"); +setName(close, "close"); +setName(closeSync, "closeSync"); +setName(constants, "constants"); +setName(copyFile, "copyFile"); +setName(copyFileSync, "copyFileSync"); +setName(cp, "cp"); +setName(cpSync, "cpSync"); +setName(createReadStream, "createReadStream"); +setName(createWriteStream, "createWriteStream"); +setName(exists, "exists"); +setName(existsSync, "existsSync"); +setName(fchmod, "fchmod"); +setName(fchmodSync, "fchmodSync"); +setName(fchown, "fchown"); +setName(fchownSync, "fchownSync"); +setName(fstat, "fstat"); +setName(fstatSync, "fstatSync"); +setName(fsync, "fsync"); +setName(fsyncSync, "fsyncSync"); +setName(ftruncate, "ftruncate"); +setName(ftruncateSync, "ftruncateSync"); +setName(futimes, "futimes"); +setName(futimesSync, "futimesSync"); +setName(lchmod, "lchmod"); +setName(lchmodSync, "lchmodSync"); +setName(lchown, "lchown"); +setName(lchownSync, "lchownSync"); +setName(link, "link"); +setName(linkSync, "linkSync"); +setName(lstat, "lstat"); +setName(lstatSync, "lstatSync"); +setName(lutimes, "lutimes"); +setName(lutimesSync, "lutimesSync"); +setName(mkdir, "mkdir"); +setName(mkdirSync, "mkdirSync"); +setName(mkdtemp, "mkdtemp"); +setName(mkdtempSync, "mkdtempSync"); +setName(open, "open"); +setName(openSync, "openSync"); +setName(promises, "promises"); +setName(read, "read"); +setName(readFile, "readFile"); +setName(readFileSync, "readFileSync"); +setName(readSync, "readSync"); +setName(readdir, "readdir"); +setName(readdirSync, "readdirSync"); +setName(readlink, "readlink"); +setName(readlinkSync, "readlinkSync"); +setName(readv, "readv"); +setName(readvSync, "readvSync"); +setName(realpath, "realpath"); +setName(realpathSync, "realpathSync"); +setName(rename, "rename"); +setName(renameSync, "renameSync"); +setName(rm, "rm"); +setName(rmSync, "rmSync"); +setName(rmdir, "rmdir"); +setName(rmdirSync, "rmdirSync"); +setName(stat, "stat"); +setName(statSync, "statSync"); +setName(symlink, "symlink"); +setName(symlinkSync, "symlinkSync"); +setName(truncate, "truncate"); +setName(truncateSync, "truncateSync"); +setName(unlink, "unlink"); +setName(unlinkSync, "unlinkSync"); +setName(unwatchFile, "unwatchFile"); +setName(utimes, "utimes"); +setName(utimesSync, "utimesSync"); +setName(watch, "watch"); +setName(watchFile, "watchFile"); +setName(write, "write"); +setName(writeFile, "writeFile"); +setName(writeFileSync, "writeFileSync"); +setName(writeSync, "writeSync"); +setName(writev, "writev"); +setName(writevSync, "writevSync"); +setName(fdatasync, "fdatasync"); +setName(fdatasyncSync, "fdatasyncSync"); +setName(openAsBlob, "openAsBlob"); +setName(opendir, "opendir"); diff --git a/src/tagged_pointer.zig b/src/tagged_pointer.zig index fdcfb3d3044a4c..ed83b302208307 100644 --- a/src/tagged_pointer.zig +++ b/src/tagged_pointer.zig @@ -126,6 +126,10 @@ pub fn TaggedPointerUnion(comptime Types: anytype) type { return null; } + pub fn typeName(this: This) ?[]const u8 { + return @tagName(this.tag()); + } + const This = @This(); pub fn assert_type(comptime Type: type) void { const name = comptime typeBaseName(@typeName(Type)); diff --git a/test/js/node/fs/cp.test.ts b/test/js/node/fs/cp.test.ts index c827fcfbb10e51..a5e21ed0006f5d 100644 --- a/test/js/node/fs/cp.test.ts +++ b/test/js/node/fs/cp.test.ts @@ -321,5 +321,5 @@ test("cp with missing callback throws", () => { expect(() => { // @ts-expect-error fs.cp("a", "b" as any); - }).toThrow(/Callback/); + }).toThrow(/"cb"/); });